Skip to content

CI Secrets Checklist

The Gotcha: Silent Empty Strings

When a GitHub Actions workflow references a secret via ${{ secrets.SECRET_NAME }}, GitHub Actions does not error if the secret is missing. Instead, it silently returns an empty string. This is by design — secrets are treated as variable expansions, not as config validation.

Impact: Code that depends on a secret will receive an empty string instead of a real value. Unless your code explicitly checks for this and fails loudly, the workflow continues and the error surfaces later:

  • A service fails to authenticate (empty API key)
  • A test doesn't run (empty test email)
  • A deployment step uses the wrong configuration (empty URL, defaults to localhost)

Example: OBJ-519 (May 14, 2026)

PR #828 blocked CI for ~14 hours because SMOKE_CLERK_EMAIL was missing from GitHub Actions secrets. The smoke test workflow referenced it on line 78 of playwright-smoke.yml:

yaml
env:
  SMOKE_CLERK_EMAIL: ${{ secrets.SMOKE_CLERK_EMAIL }}

The auth.staging.setup.ts setup project checked for it and threw:

Error: SMOKE_CLERK_EMAIL is required for the staging setup project (a +clerk_test staging Clerk user)

But this error only surfaced when the first real PR ran the workflow. The problem had existed since Phase 91 (which introduced the reference) and was silently returning an empty string on every sandbox/CI validation run. GitHub Actions' actionlint linter passed (it checks syntax, not secret existence). The staging.yml and production.yml workflows never ran, so no Docker image was built. When the PR merged and playwright-smoke.yml fired for the first time, the missing secret caused a complete CI failure.

Root cause: The acceptance criteria for Phase 91 did not include "confirm secret exists AND smoke workflow passes on a live CI run." The planner had marked SMOKE_CLERK_EMAIL as [ASSUMED] — Planner must confirm this is provisioned in the phase notes, but the assumption was never verified.

The Rule

Any new secret-dependent workflow step needs an acceptance criterion:

"Confirm secret X is present in Settings → Secrets and variables → Actions and a live CI run (PR or tag push) passes."

This rules out:

  • ❌ Workflows that are validated only with actionlint or other linters (they check syntax, not existence)
  • ❌ Workflows that are validated only on scheduled/manual dispatch with no real PR runs
  • ❌ Workflows that have an [ASSUMED] note in planning docs that was never verified

Responsible party: The developer/planner who wires the secret into the workflow is responsible for confirming it exists in the repo settings and for ensuring a live CI run validates it.

Current Required Secrets (as of v4.8.4)

Smoke Tests

Required for playwright-smoke.yml (both staging and production smoke runs):

  • SMOKE_CLERK_EMAIL — A +clerk_test staging Clerk user email. Format: anything+clerk_test@example.com. Used by ionic_frontend/tests/smoke-playwright/setup/auth.staging.setup.ts to sign in and establish session state for test runs. Required.
  • SMOKE_FALLBACK_EMAIL — A fallback test email for certain test suites. Used by smoke specs that require multiple user accounts. Required (though may be the same address as SMOKE_CLERK_EMAIL in simple setups).

Setup:

  1. Create a +clerk_test test user in the staging Clerk instance (or re-use an existing one)
  2. Add SMOKE_CLERK_EMAIL (e.g., test+clerk_test@objectuve.com) to GitHub Actions secrets
  3. Add SMOKE_FALLBACK_EMAIL (e.g., test-fallback+clerk_test@objectuve.com) to GitHub Actions secrets
  4. Verify by running the Playwright Smoke Tests workflow manually or opening a PR

Production Deployment

Required for production.yml:

  • SENTRY_DSN_API — Sentry DSN for backend error tracking. Format: https://key@sentry-instance/id. Used by Rails API to report exceptions. Required.
  • LITELLM_URL_PRODUCTION — LiteLLM proxy URL for production. Format: https://litellm.internal/.... Used by Agent Runner to route LLM calls. Required.
  • GCP_SERVICE_ACCOUNT — GCP service account JSON (for infrastructure deployments). Required for deploying to GCP. Passed to google-github-actions/auth.
  • GCP_WORKLOAD_IDENTITY_PROVIDER — GCP Workload Identity Federation provider. Required for OIDC-based auth to GCP. Passed to google-github-actions/auth.

Staging Deployment

Required for staging.yml:

  • SENTRY_DSN_API — Sentry DSN for backend error tracking (same DSN may be shared with production or a separate staging instance). Required.
  • LITELLM_URL_STAGING — LiteLLM proxy URL for staging. Format: https://litellm-staging.internal/.... Used by staging Agent Runner to route LLM calls. Required.
  • GCP_SERVICE_ACCOUNT — Same GCP service account used by production; required for staging Cloud Run deployments.
  • GCP_WORKLOAD_IDENTITY_PROVIDER — Same Workload Identity provider; required for OIDC-based GCP auth on staging.

Sentry Release Tracking (staging.yml, production.yml, admin.yml)

Used by scripts/sentry-create-release.sh to create a Sentry release + deploy marker for client/api/admin after each successful deploy. Fail-soft — a missing token or repo Variable emits a ::warning:: and never fails the deploy. See OBJ-2099.

  • SENTRY_AUTH_TOKEN — Sentry auth token (org-scoped, project:read + project:releases + event:read). Secret. Not yet provisioned — until it is, the release/deploy-marker steps no-op with a warning.
  • SENTRY_ORG — Sentry organization slug (objectuve). Repo Variable. Set.
  • SENTRY_PROJECT_CLIENT — Sentry project slug for the ionic frontend (objectuve-client). Repo Variable. Set.
  • SENTRY_PROJECT_API — Sentry project slug for the Rails API (objectuve-api). Repo Variable. Set.
  • SENTRY_PROJECT_ADMIN — Sentry project slug for the admin dashboard (objectuve-admin). Repo Variable. Not yet provisioned — until it is, admin's release/deploy-marker step no-ops with a warning (client/API are unaffected, since each project is independently skippable).

Release Health Watch (staging.yml)

  • SENTRY_ISSUES_API_TOKEN — Sentry API token, distinct credential from SENTRY_AUTH_TOKEN above (needs event:read, not project:releases). Used by the release-health-watch job's scripts/check-release-health.mjs to poll for a new, unresolved, release-attributed Sentry error in the deploy+0 → deploy+RELEASE_HEALTH_WINDOW_MINUTES window after a staging deploy (OBJ-2264/OBJ-2267 — see deployment.md § Release-health watch coverage window for the coverage-window contract). Provisioned — the job previously fail-softed on every run with ::warning::SENTRY_ISSUES_API_TOKEN not provisioned (staging.yml:1344-1345); live coverage is unblocked as of this secret's provisioning (OBJ-3593).

Mobile CI (Optional)

Required for mobile-android.yml and mobile-ios.yml (manual dispatch only):

  • ANDROID_KEYSTORE_BASE64 — Android keystore encoded as base64
  • ANDROID_KEY_ALIAS — Keystore key alias
  • ANDROID_KEY_PASSWORD — Keystore key password
  • ANDROID_KEYSTORE_PASSWORD — Keystore password
  • GOOGLE_PLAY_JSON_KEY_BASE64 — Google Play service account key, base64-encoded. Used by mobile-android.yml for Android release publishing to the Play Store. Required for Android distribution builds.
  • APPLE_ID — Apple ID for App Store Connect
  • ASC_KEY_ID — App Store Connect key ID
  • ASC_ISSUER_ID — App Store Connect issuer ID
  • ASC_KEY_CONTENT — App Store Connect private key (base64)
  • MATCH_GIT_URL — Fastlane Match Git repository
  • MATCH_PASSWORD — Fastlane Match password
  • MATCH_GIT_PRIVATE_KEY — SSH private key for Fastlane Match Git repo access. Used by mobile-ios.yml for iOS code signing certificate/profile management. Required for iOS distribution builds.
  • APPLE_TEAM_ID — Apple Developer Team ID
  • GOOGLE_IOS_CLIENT_ID — iOS OAuth client ID from Google Cloud Console (must be the iOS client type, not Web or Android). Resolves ionic_frontend/ios/google-signin.xcconfig's GOOGLE_IOS_CLIENT_ID_OVERRIDE, which feeds Info.plist's GIDClientID. Not yet provisioned — until it is, mobile-ios.yml's pre-flight step fails any testflight/appstore dispatch before archive/upload, naming this secret. Missing it is what shipped native Google Sign-In 100% broken in production from 2026-08-16 (Sentry OBJECTUVE-CLIENT-C2) with no CI signal, because the override had an empty default and nothing ever set it — see gotchas.md § An empty xcconfig *_DEFAULT with an unwired *_OVERRIDE ships silently broken to every real build. Required for iOS distribution builds. (OBJ-2772)
  • GOOGLE_IOS_REVERSED_CLIENT_ID — The same iOS OAuth client's reversed-ID form, used as the CFBundleURLSchemes entry (Info.plist:101) that the Google Sign-In SDK's OAuth return path calls back into. Resolves google-signin.xcconfig's GOOGLE_IOS_REVERSED_CLIENT_ID_OVERRIDE. Same provisioning source and same missing-secret gate as GOOGLE_IOS_CLIENT_ID above — both or neither. Not yet provisioned. Required for iOS distribution builds. (OBJ-2772)

(Only required if you're building and distributing mobile apps via Fastlane.)

Smoke Test Authentication (Optional)

Read by playwright-smoke.yml's Run Playwright Smoke step, rooted at ionic_frontend/tests/smoke-playwright/:

  • ACHIEVEMENTS_EMPTY_EMAIL — Email for a fallback-auth account (smoke-achievements-empty@objectuve.com) seeded with zero unlocked achievements. Signs in via a Fallback <FALLBACK_AUTH_SECRET>:<email> token (helpers/env.ts#buildFallbackToken), not Clerk. Used by specs/gamification/11-achievements-empty.spec.ts (and the overlapping legacy specs/gamification/achievements.spec.ts) to assert the empty-state hero, "Begin here" card, and Inner Drawer row. Optional — each spec does test.skip(!email, ...), so an absent value skips rather than fails.
  • ACHIEVEMENTS_POPULATED_EMAIL — Same fallback-auth pattern as above, for an account (smoke-achievements-populated@objectuve.com) seeded with unlocked achievements and active goal progress. Used by specs/gamification/12-achievements-populated.spec.ts (and the same legacy achievements.spec.ts) to assert the populated hero, "Next on the shelf" card, and achievements grid. Optional, same skip behavior.
  • CLERK_SECRET_KEY — Clerk backend secret key, used by smoke setup to create test users on-the-fly if they don't exist. Should match the staging Clerk instance's backend secret. Optional for production smoke runs (fall back to SMOKE_FALLBACK_EMAIL); required for staging smoke setups.
  • CLERK_SECRET_KEY_PROD — Same role as CLERK_SECRET_KEY above, scoped to the production Clerk instance. playwright-smoke.yml selects between the two by smoke_env: secrets.CLERK_SECRET_KEY_PROD when the run targets production, secrets.CLERK_SECRET_KEY otherwise (playwright-smoke.yml:410).

GitHub & Third-Party Integration

  • GITHUB_TOKEN — GitHub Actions default token (auto-provided, no manual setup needed). Read access for checkout, write access for PR comments.
  • GCP_SERVICE_ACCOUNT — GCP service account JSON (for infrastructure deployments). Required if deploying to GCP. Passed to google-github-actions/auth in production.yml and staging.yml.
  • GCP_WORKLOAD_IDENTITY_PROVIDER — GCP Workload Identity Federation provider. Required for OIDC-based auth to GCP. Passed to google-github-actions/auth.

(These are infrastructure-level and typically set once per repository. Check GitHub's Actions secrets UI for current values.)

Load Tests (load-test.yml, monthly cron)

  • RAILS_WEBHOOK_SECRET — HMAC-SHA256 signing secret Agent Runner uses to sign its result callback to POST /webhooks/ai-workforce (agent_runner/src/webhook/sender.ts, AUTH_MODE=secret). The L4 script (load_tests/l4_ai_workforce_webhook.js) exercises this same signed-webhook path directly. Required — the workflow's own "Verify required secrets" step (load-test.yml:55-69) fails the job fast with a named error if it's absent, rather than letting the L4 step fail later with a less specific one.

Scheduled Release Train (scheduled-release.yml)

  • SLACK_BUDGET_ALERT_WEBHOOK_URL — Slack incoming webhook URL, reused from the same secret backing production/staging Rails alerts. Used by the "Alert — dead or misconfigured release train" step to notify on a dormant (missing RELEASE_TOKEN) or failed (invalid RELEASE_TOKEN) run (OBJ-1349). Fail-soft — an absent value just skips the alert with a ::warning::, never fails the workflow. See multica-github-auth.md § Diagnosing a dead release train for the incident this closed — that doc's own "Nobody was notified" note still says this secret is unprovisioned and the alert is a no-op, which is now stale (flagged separately, not fixed here — out of this task's file scope).

How to Add a New Secret

When adding a new secret to a workflow:

  1. Add the secret to GCP Secret Manager or another secret provider:

    bash
    echo "value" | gcloud secrets create my-secret-name --data-file=-
  2. Add it to GitHub Actions secrets:

    • Go to Settings → Secrets and variables → Actions
    • Click New repository secret
    • Name: MY_SECRET_NAME (SCREAMING_SNAKE_CASE)
    • Value: the secret value
    • Click Add secret
  3. Reference it in the workflow:

    yaml
    env:
      MY_ENV_VAR: ${{ secrets.MY_SECRET_NAME }}
  4. Create a test/verification step:

    • If the secret is used by a test, add a check: if [ -z "$MY_ENV_VAR" ]; then echo "ERROR: MY_SECRET_NAME not set"; exit 1; fi
    • If the secret is used by a service deploy, test the deployment on staging first
    • Document the secret in this file (so future contributors know about it)
  5. Verify on CI:

    • Open a PR or run the workflow manually
    • Confirm the workflow passes and the secret is used correctly
    • Do NOT merge until verified

Checklist: Before Deploying a Workflow Change

  • [ ] All new secrets are present in GitHub Actions Settings → Secrets and variables → Actions
  • [ ] Workflow references are spelled correctly (exact match to secret name, case-sensitive)
  • [ ] At least one live CI run (PR or tag push) has passed with the secrets in place
  • [ ] The code that uses the secret has an explicit check (e.g., if (!process.env.VARIABLE) throw new Error(...))
  • [ ] This file has been updated with the new secret name and purpose (so future contributors know)

Additional References

  • Deployment pipeline details: See deployment.md — includes production/staging safeguards, release process, rollback procedures
  • GitHub Actions workflows: See .github/workflows/ for full workflow definitions
  • Related issue (OBJ-519): Example of a 14-hour CI block due to missing SMOKE_CLERK_EMAIL. Now preventable with explicit live-run verification.

Last updated: 2026-09-14

Loading…