fix(ci): pass the Sentry token as a secret-file, not a literal string #143

Merged
ullrich merged 1 commit from fix-sentry-secret-files into main 2026-09-11 13:31:36 +00:00
Owner

Fixes the 401 in #5. The token was never wrong.

Root cause

docker/build-push-action has two different inputs:

  • secrets: — takes literal values (MY_TOKEN=abc123)
  • secret-files: — takes paths (MY_TOKEN=./token.txt)

Both CD workflows used the first with a path:

secrets: |
  SENTRY_AUTH_TOKEN=/tmp/sentry_token

So the build received the 17-character string /tmp/sentry_token as the token. The Dockerfile cats it back out of /run/secrets/, sentry-cli sends it as the credential, and Sentry answers Invalid token (http status: 401) — correctly. The decrypted token never entered the build.

The build log shows the signature:

--secret id=SENTRY_AUTH_TOKEN,src=/tmp/docker-actions-toolkit-Y6sWb8/tmp-189-HUtub87R06pM

That src is an action-generated temp file holding the literal value. With secret-files:, src points at /tmp/sentry_token itself.

Why this took three theories to find

The 401 is indistinguishable from a bad credential, so every hypothesis was about the credential. Each was eliminated by measurement:

Theory Verdict
Token scopes too narrow (the original #5 diagnosis) Scopes return 403, not 401
EU data region not configured #140 pinned de.sentry.io; the 401 did not change
Ambiguous grep concatenating two keys Only one key in secrets.app.env matches
Token revoked, mistyped, or quoted Returns 200 against both sentry.io and de.sentry.io; the stored value is a well-formed 71 characters with no quotes

With the credential proven good and the 401 still happening, the only place left was the transport.

Changes

  • secrets:secret-files: in cd-apps.yml and cd-staging.yml, with a comment recording the distinction.
  • Corrects the comment #140 left in both vite.config.ts files. That PR claimed the region was the cause. It was not, and the next reader should not believe it. The url pin itself stays — our org is in the EU, the pin is harmless, and it is now labelled as defensive rather than curative.

Verification

pnpm typecheck, pnpm lint and pnpm test pass. Both workflow files parse, and the with: block resolves to secret-files with secrets absent.

I did not run pnpm test:e2e locally — it needs the full Docker stack, and nothing here can reach it. CI runs it.

The real check is this PR's own staging build: cd-staging runs the PR branch's workflow file, so the preview build exercises the fix. A build log with no Invalid token line, and a release appearing in Sentry for the preview SHA, confirms it. I will watch the run and report.

Still open in #5

A failed upload still does not fail the build — run 1459 finished green with two 401s in its log. That follow-up keeps #5 open, and it is what stops this class of silent rot.

🤖 Generated with Claude Code

Fixes the 401 in #5. The token was never wrong. ## Root cause `docker/build-push-action` has two different inputs: - `secrets:` — takes literal **values** (`MY_TOKEN=abc123`) - `secret-files:` — takes **paths** (`MY_TOKEN=./token.txt`) Both CD workflows used the first with a path: ```yaml secrets: | SENTRY_AUTH_TOKEN=/tmp/sentry_token ``` So the build received the 17-character string `/tmp/sentry_token` **as the token**. The Dockerfile `cat`s it back out of `/run/secrets/`, `sentry-cli` sends it as the credential, and Sentry answers `Invalid token (http status: 401)` — correctly. The decrypted token never entered the build. The build log shows the signature: ``` --secret id=SENTRY_AUTH_TOKEN,src=/tmp/docker-actions-toolkit-Y6sWb8/tmp-189-HUtub87R06pM ``` That `src` is an action-generated temp file holding the literal value. With `secret-files:`, `src` points at `/tmp/sentry_token` itself. ## Why this took three theories to find The 401 is indistinguishable from a bad credential, so every hypothesis was about the credential. Each was eliminated by measurement: | Theory | Verdict | |---|---| | Token scopes too narrow (the original #5 diagnosis) | Scopes return 403, not 401 | | EU data region not configured | #140 pinned `de.sentry.io`; the 401 did not change | | Ambiguous `grep` concatenating two keys | Only one key in `secrets.app.env` matches | | Token revoked, mistyped, or quoted | Returns **200** against both `sentry.io` and `de.sentry.io`; the stored value is a well-formed 71 characters with no quotes | With the credential proven good and the 401 still happening, the only place left was the transport. ## Changes - `secrets:` → `secret-files:` in `cd-apps.yml` and `cd-staging.yml`, with a comment recording the distinction. - Corrects the comment #140 left in both `vite.config.ts` files. That PR claimed the region was the cause. It was not, and the next reader should not believe it. The `url` pin itself stays — our org is in the EU, the pin is harmless, and it is now labelled as defensive rather than curative. ## Verification `pnpm typecheck`, `pnpm lint` and `pnpm test` pass. Both workflow files parse, and the `with:` block resolves to `secret-files` with `secrets` absent. I did not run `pnpm test:e2e` locally — it needs the full Docker stack, and nothing here can reach it. CI runs it. The real check is this PR's own staging build: `cd-staging` runs the **PR branch's** workflow file, so the preview build exercises the fix. A build log with no `Invalid token` line, and a release appearing in Sentry for the preview SHA, confirms it. I will watch the run and report. ## Still open in #5 A failed upload still does not fail the build — run 1459 finished green with two 401s in its log. That follow-up keeps #5 open, and it is what stops this class of silent rot. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(ci): pass the Sentry token as a secret-file, not a literal string
All checks were successful
CD Staging / Build & Push Docker Images (pull_request) Has been skipped
CD Staging / Build & Push Docker Images-1 (pull_request) Has been skipped
CD Staging / Deploy Staging (pull_request) Has been skipped
CD Staging / Deploy PR Preview (pull_request) Has been skipped
CI / Dockerfile Package Check (pull_request) Successful in 15s
CI / Security Scan (pull_request) Successful in 49s
CI / Visual Tests (pull_request) Successful in 1m33s
CI / Checks (pull_request) Successful in 5m15s
CI / Journal Image Smoke Test (pull_request) Successful in 3m38s
CI / E2E Tests (pull_request) Successful in 3m47s
Cancel superseded CI / Cancel in-flight CI (pull_request) Successful in 14s
CD Staging / Tear Down PR Preview (pull_request) Successful in 36s
6c5a246f16
`docker/build-push-action`'s `secrets:` input takes literal VALUES;
`secret-files:` takes PATHS. Both CD workflows used `secrets:` with a path:

    secrets: |
      SENTRY_AUTH_TOKEN=/tmp/sentry_token

So the build received the 17-character string "/tmp/sentry_token" as the
token itself. The Dockerfile cat'd it back out, sentry-cli sent it as the
credential, and Sentry answered `Invalid token (http status: 401)` —
correctly. The real token never reached the build.

This accounts for every observation in #5 at once: a token that returns 200
against both sentry.io and de.sentry.io, a well-formed 71-char value in
secrets.app.env, a clean grep/cut extraction, and a 401 that no rotation and
no region setting could have fixed. It has been broken since this plumbing
was written, which is why there was never a working upload to compare with.

Also corrects the comment #140 left behind: the EU region pin is defensive,
not the fix.

Refs #5 — the issue stays open for its follow-up: a failed upload must fail
the build, so this cannot rot silently again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ullrich deleted branch fix-sentry-secret-files 2026-09-11 13:31:36 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
trails-cool/trails!143
No description provided.