The decision
The founder ruled on #9339 (2026-09-20) that the live BETTER_AUTH_SECRET stays with him and no
agent ever gets a copy. This issue is the sibling that ruling named: how does an agent seat get a
signing key for a preview worker, so :auth and :auth-caylak renders stop ending on a person?
Two named routes are on the table, and only the founder picks:
- Commit a fixed preview signing key to the repo. Every agent on every machine has it with no
setup. A forged preview login then reaches only that preview's own throwaway database. Preview
URLs are public, so anyone could sign in to a preview as a test user.
- Keep it secret and place it per machine as an environment variable on each machine that runs
agents. One paste per machine, rotated if it leaks. No repo copy, but a new machine is a manual
step before any signed-in render works.
Either answer is a founder ruling, not a build. That is why this leaves triage ready-for:human.
What is true at origin/main
Read at main, not in a lane's snapshot:
infra/ci-credentials/github.ts mints one repo-wide BETTER_AUTH_SECRET (a stable Random
persisted in the ci-credentials stack state) and pushes it to GitHub as a write-only Actions
secret. Its docblock names it as one of four roster secrets.
apps/web/worker/config.ts binds it as Config.redacted(ENV_BINDINGS.betterAuthSecret), a
Cloudflare secret_text binding that does not read back at request time, with no default.
.github/workflows/deploy.yml:422 passes the same secrets.BETTER_AUTH_SECRET into every
auth-binding leg — matrix.needs-auth && secrets.BETTER_AUTH_SECRET || '' — so a pr-<n> preview
worker and the production worker verify against the identical key. There is no per-stage branch.
packages/fabrika-cli/src/capture/auth.ts signs the better-auth session cookie with whatever the
caller names (signSessionToken) and refuses an empty value or the insecure_ placeholder
prefix (classifyAuthSecret, PLACEHOLDER_SECRET_PREFIX).
packages/fabrika-cli/src/review-ui/render-verb.ts takes --auth-secret-from <file> and, on a
visitor answer, points the caller back at the ci-credentials state behind $ALCHEMY_PASSWORD —
a route no agent seat holds.
Triage note — the open check the founder named is partly answered. He asked whether a preview
database can ever hold real user data. ADR
0349 records that every preview runs on
its own per-PR D1 named phoenix-phoenix-db-pr-<n>-…, and that preview-seed test-account refuses
any target whose Cloudflare-recorded name lacks the -pr- segment — production and every named
stage are caught, fail closed. The same ADR records that the per-PR preview D1 also carries the
e2e suite's own sign-ups, which are test identities, not customers. So a preview holds no
production user rows. What is not verified, and what route 1 still rests on, is whether a forged
session signed with a committed preview key could reach anything beyond a preview origin — that is
the check the chosen route owes.
Why it matters
Four lanes have parked on the signing side already (#9276, #9279, #9288's fourth criterion, and
#9423 / #8776). #9281 and #9288 fix the account side of signed-in captures; neither touches the
key. With #9339 ruled, this is the remaining half, and until it is answered every auth-gated
review-ui verdict still ends on the founder.
Triage note — why standalone, not folded
Searched the board for an owner on this surface (preview-only auth signing secret for agents).
The nearest live tickets are #9339 (the founder-only ruling, now answered), #9337 (the five-surface
duplication of the secret's provenance), #9281 / #9288 / #9283 (the seeded-account and park-recipe
side) and #7777 (an out-of-band worker secret flake). None owns credential supply policy, and
#9339's own ruling comment says a separate ticket for a test-only key is expected. Folding this
into a buildable ticket is what left #9339 unreachable through four review rounds, so it is minted
standalone.
Pointers
Acceptance criteria
Triage note: the criteria cover both shapes the ruling can take. Only the branch the founder picks
is graded.
Amendment — 2026-09-21: the ruling landed, so route 2 is off the table
Nothing above is withdrawn. This block is appended because the founder ruled route 1 on
2026-09-21 (#9533 (comment), marked
decision-ruled in the comment below it). The list above was written while both routes were live,
so its first row asks for a ruling that now exists and its last row grades a branch nobody will
build. The list below replaces it as the contract a builder works to, and grades route 1 alone.
The ruling, restated so a builder need not reconstruct it: preview workers get their own signing
key, that key is committed to the repo, the production BETTER_AUTH_SECRET stays founder-only per
#9339, and the committed key must never be accepted by the production worker. The accepted tradeoff
is that preview logins are open to anyone who reads the repo.
Acceptance criteria
Triage note: the ruling itself is already recorded, so it is stated above rather than carried as a
row to tick. No production secret is read, written or rotated anywhere in this work.
Original report (verbatim)
Summary
One repo-wide BETTER_AUTH_SECRET signs login sessions for every deployed web worker, preview and production alike. The founder ruled on 2026-09-20 (#9339) that this live secret stays with him only and no agent is ever given a copy. That leaves signed-in screenshot checks on preview deploys as founder work, unless previews verify against a separate secret that is safe to hand to an agent.
What I was doing
Recording the founder's ruling on #9339, which asked whether an agent should be given a route to the deployed auth secret so it can sign a session and capture a signed-in page.
What I observed
From #9339, read at main by its triager:
infra/ci-credentials/github.ts mints one repo-wide BETTER_AUTH_SECRET into the ci-credentials stack's state and pushes it to GitHub as a write-only Actions secret.
apps/web/worker/config.ts binds it as secret_text, which does not read back.
packages/fabrika-cli/src/capture/auth.ts (classifyAuthSecret, signSessionToken) signs the session cookie with whatever secret the caller names, and refuses an empty or insecure_ placeholder value.
- The one readable copy sits behind the ci-credentials stack password, which no agent seat carries.
So the secret a preview worker verifies against is the same one production verifies against. A copy that lets an agent sign a preview session would also let it forge a production login for any user. That is why the ruling keeps it founder-only.
Why it matters
The founder ruled elsewhere that he wants out of the pipeline's day-to-day loop. With the ruling on #9339, every :auth and :auth-caylak rendered check still ends on him. #9281 and #9288 (preview test accounts) fix the account side of signed-in captures but not the signing side. Whether previews can take a different secret without breaking anything that shares sessions across stages is not verified.
Pointers
Suggested next step (non-binding)
A guess: mint a second secret used only by preview stages, keep the production one where it is, and give agent seats a read route to the preview one. A forged preview session then reaches only seeded test data. Worth checking first: whether any flow expects a session signed on one stage to verify on another.
Filed by an agent · session 1114d1ad-d769-4169-bb00-4d899c13bc18 · branch main · 2026-09-20T20:53:45Z
The decision
The founder ruled on #9339 (2026-09-20) that the live
BETTER_AUTH_SECRETstays with him and noagent ever gets a copy. This issue is the sibling that ruling named: how does an agent seat get a
signing key for a preview worker, so
:authand:auth-caylakrenders stop ending on a person?Two named routes are on the table, and only the founder picks:
setup. A forged preview login then reaches only that preview's own throwaway database. Preview
URLs are public, so anyone could sign in to a preview as a test user.
agents. One paste per machine, rotated if it leaks. No repo copy, but a new machine is a manual
step before any signed-in render works.
Either answer is a founder ruling, not a build. That is why this leaves triage
ready-for:human.What is true at
origin/mainRead at main, not in a lane's snapshot:
infra/ci-credentials/github.tsmints one repo-wideBETTER_AUTH_SECRET(a stableRandompersisted in the ci-credentials stack state) and pushes it to GitHub as a write-only Actions
secret. Its docblock names it as one of four roster secrets.
apps/web/worker/config.tsbinds it asConfig.redacted(ENV_BINDINGS.betterAuthSecret), aCloudflare
secret_textbinding that does not read back at request time, with no default..github/workflows/deploy.yml:422passes the samesecrets.BETTER_AUTH_SECRETinto everyauth-binding leg —
matrix.needs-auth && secrets.BETTER_AUTH_SECRET || ''— so apr-<n>previewworker and the production worker verify against the identical key. There is no per-stage branch.
packages/fabrika-cli/src/capture/auth.tssigns the better-auth session cookie with whatever thecaller names (
signSessionToken) and refuses an empty value or theinsecure_placeholderprefix (
classifyAuthSecret,PLACEHOLDER_SECRET_PREFIX).packages/fabrika-cli/src/review-ui/render-verb.tstakes--auth-secret-from <file>and, on avisitor answer, points the caller back at the ci-credentials state behind
$ALCHEMY_PASSWORD—a route no agent seat holds.
Triage note — the open check the founder named is partly answered. He asked whether a preview
database can ever hold real user data. ADR
0349 records that every preview runs on
its own per-PR D1 named
phoenix-phoenix-db-pr-<n>-…, and thatpreview-seed test-accountrefusesany target whose Cloudflare-recorded name lacks the
-pr-segment — production and every namedstage are caught, fail closed. The same ADR records that the per-PR preview D1 also carries the
e2e suite's own sign-ups, which are test identities, not customers. So a preview holds no
production user rows. What is not verified, and what route 1 still rests on, is whether a forged
session signed with a committed preview key could reach anything beyond a preview origin — that is
the check the chosen route owes.
Why it matters
Four lanes have parked on the signing side already (#9276, #9279, #9288's fourth criterion, and
#9423 / #8776). #9281 and #9288 fix the account side of signed-in captures; neither touches the
key. With #9339 ruled, this is the remaining half, and until it is answered every auth-gated
review-uiverdict still ends on the founder.Triage note — why standalone, not folded
Searched the board for an owner on this surface (
preview-only auth signing secret for agents).The nearest live tickets are #9339 (the founder-only ruling, now answered), #9337 (the five-surface
duplication of the secret's provenance), #9281 / #9288 / #9283 (the seeded-account and park-recipe
side) and #7777 (an out-of-band worker secret flake). None owns credential supply policy, and
#9339's own ruling comment says a separate ticket for a test-only key is expected. Folding this
into a buildable ticket is what left #9339 unreachable through four review rounds, so it is minted
standalone.
Pointers
infra/ci-credentials/github.ts,apps/web/worker/config.ts.github/workflows/deploy.yml(theneeds-authmatrix leg)packages/fabrika-cli/src/capture/auth.ts,packages/fabrika-cli/src/review-ui/render-verb.ts.decisions/0349-preview-seed-fence-keys-on-d1-name.mdAcceptance criteria
pr-<n>stages only, leaving production on the founder-held secret [evidence: a livereview-ui renderof an:authsurface on a preview returning a signed-in capture rather than exit 11]Triage note: the criteria cover both shapes the ruling can take. Only the branch the founder picks
is graded.
Amendment — 2026-09-21: the ruling landed, so route 2 is off the table
Nothing above is withdrawn. This block is appended because the founder ruled route 1 on
2026-09-21 (#9533 (comment), marked
decision-ruledin the comment below it). The list above was written while both routes were live,so its first row asks for a ruling that now exists and its last row grades a branch nobody will
build. The list below replaces it as the contract a builder works to, and grades route 1 alone.
The ruling, restated so a builder need not reconstruct it: preview workers get their own signing
key, that key is committed to the repo, the production
BETTER_AUTH_SECRETstays founder-only per#9339, and the committed key must never be accepted by the production worker. The accepted tradeoff
is that preview logins are open to anyone who reads the repo.
Acceptance criteria
.github/workflows/deploy.yml'sneeds-authmatrix leg is stage-keyed: apr-<n>stage gets the committed preview key, and production and every named stage keepsecrets.BETTER_AUTH_SECRETpackages/fabrika-cli/src/capture/auth.tsaccepts the committed preview key:classifyAuthSecretandPLACEHOLDER_SECRET_PREFIXdo not refuse the value that is actually committedpackages/fabrika-cli/src/review-ui/render-verb.tsresolves the preview key from the repo with no--auth-secret-fromand no ci-credentials read, and its visitor-answer message stops pointing a seat at the$ALCHEMY_PASSWORDroute no agent holdsinfra/ci-credentials/github.tsstill mints and pushes the productionBETTER_AUTH_SECRETunchanged, and its roster docblock says the preview key is not one of its secrets.decisions/0349-preview-seed-fence-keys-on-d1-name.md), preview logins are open to anyone who reads the repo, and the production worker rejects the committed keyreview-ui renderof an:authsurface on this PR's own preview returns a signed-in capture rather than exit 11 [evidence: that render's own output on the PR's preview deploy, quoted in the PR body or in a comment on this issue]Triage note: the ruling itself is already recorded, so it is stated above rather than carried as a
row to tick. No production secret is read, written or rotated anywhere in this work.
Original report (verbatim)
Summary
One repo-wide
BETTER_AUTH_SECRETsigns login sessions for every deployed web worker, preview and production alike. The founder ruled on 2026-09-20 (#9339) that this live secret stays with him only and no agent is ever given a copy. That leaves signed-in screenshot checks on preview deploys as founder work, unless previews verify against a separate secret that is safe to hand to an agent.What I was doing
Recording the founder's ruling on #9339, which asked whether an agent should be given a route to the deployed auth secret so it can sign a session and capture a signed-in page.
What I observed
From #9339, read at main by its triager:
infra/ci-credentials/github.tsmints one repo-wideBETTER_AUTH_SECRETinto the ci-credentials stack's state and pushes it to GitHub as a write-only Actions secret.apps/web/worker/config.tsbinds it assecret_text, which does not read back.packages/fabrika-cli/src/capture/auth.ts(classifyAuthSecret,signSessionToken) signs the session cookie with whatever secret the caller names, and refuses an empty orinsecure_placeholder value.So the secret a preview worker verifies against is the same one production verifies against. A copy that lets an agent sign a preview session would also let it forge a production login for any user. That is why the ruling keeps it founder-only.
Why it matters
The founder ruled elsewhere that he wants out of the pipeline's day-to-day loop. With the ruling on #9339, every
:authand:auth-caylakrendered check still ends on him. #9281 and #9288 (preview test accounts) fix the account side of signed-in captures but not the signing side. Whether previews can take a different secret without breaking anything that shares sessions across stages is not verified.Pointers
infra/ci-credentials/github.tsapps/web/worker/config.tspackages/fabrika-cli/src/capture/auth.tsSuggested next step (non-binding)
A guess: mint a second secret used only by preview stages, keep the production one where it is, and give agent seats a read route to the preview one. A forged preview session then reaches only seeded test data. Worth checking first: whether any flow expects a session signed on one stage to verify on another.
Filed by an agent · session
1114d1ad-d769-4169-bb00-4d899c13bc18· branchmain· 2026-09-20T20:53:45Z