broker: delete the credential broker, and keep the machine bearer it hid - #244
Merged
Conversation
The broker is not a supported authentication mode. It enrolled hosted boxes automatically and it overrode a working native Codex login: `blitz-cred register` wrote `model_provider = "blitz"` into `.codex/config.toml`, and a box whose watcher was not running then answered HTTP 401. `plans/BROKER-RETIREMENT.md` (PR #198) planned this in two releases, behind a fleet-convergence gate and a credential-custody audit. Both control planes hold zero broker rows, verified today: prod `51bebbfa-…` and canary `8a3458ff-…` each report broker_boxes 0, broker_members 0, broker_keys 0, and canary reports no machine with a broker_box_id. Nothing is enrolled, so nothing needs draining and no broker disk holds a member's credential. One release does it. Deleted: `packages/broker` whole, the `register` and `watch` s6 services, the `blitz-register` boot script, both token helpers, the broker mint in the `claude` shim, the broker probes in `blitz-codex-session`, the register poke in cloud bootstrap, `core/registry.ts` and its four routes, the broker wire types, `BoxIdentity.isBroker`, the broker key cleanup in the destroy and orphan janitors, the broker CI job and the OCI publish. `/boxes/:id/feed` and the constant workspace environment route go with it. Both were compatibility surfaces for one caller, and that caller was the broker. Kept: `blitz-cred api-token`. Agent rules and the Git credential helper call it, and it is machine authentication rather than a broker feature. It moves to `packages/box/credential-helper` with the primitives it reaches, and keeps its name. Claude and Codex now read their own stores under HOME. Migration 0053 drops `broker_keys`, `broker_members` and `broker_boxes`, and rebuilds `machines` and `boxes` without the columns that referenced them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GocywGPN9b8DrcBXR9C4id
Deleting a core/ file needs four hand-written edits in three files. blitzdev-emitter.test.ts is gated behind BLITZDEV_MANAGED=1, so a plain npm test does not reach it. Its list and its length now match the other two. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GocywGPN9b8DrcBXR9C4id
5 tasks
The new script shipped 0644. s6 answered `s6-applyuidgid: fatal: unable to exec /usr/local/libexec/blitz-credential-refresh: Permission denied`, and only the box image build saw it. Nothing else asserted the mode. rootfs-exec-bits.test.ts now checks every script under usr/local/bin and usr/local/libexec. s6-rc.d run and up files stay 0644 on purpose: s6 runs them through its own launcher. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GocywGPN9b8DrcBXR9C4id
pythonlearner1025
added a commit
that referenced
this pull request
Sep 6, 2026
Resolves the broker deletion (#244) against the periodic CLI updater: the claude shim stays exec-only, the payload list keeps the agent-cli-update service and drops the register dependency, and the docs describe the update service as the model delivery path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01So15XYnmh9Hfx9xdNgG1Yu
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What part this touches
A BlitzOS box authenticates two different things, and they were tangled together
in one Go package.
plane.
blitz-cred api-tokenturns it into a bearer for/agent/*. Agentrules and the Git credential helper call it. This is load-bearing.
logins and minted them back to boxes over SSH. A box enrolled into it
automatically at boot, through the
registers6 service.Both lived in
packages/broker, so the second could not be deleted withoutfirst rescuing the first.
The bug
The broker is not a supported authentication mode, and it overrode one that
works.
blitz-cred registerwrote this into the member's.codex/config.toml:With the broker watcher not running, a fresh token never arrived and every
Codex request returned HTTP 401 from
https://chatgpt.com/backend-api/codex/responses.codex login statusstillreported
Logged in using ChatGPT, so this did not look like a logout. EveryLody Codex session shared that home, so the failure was workspace-wide.
plans/BROKER-RETIREMENT.md(PR #198) recorded that incident on 2026-09-02 andplanned the removal in two releases, behind a fleet-convergence gate and a
credential-custody audit.
The fix
The plan's two releases collapse into one, because its gates have nothing to
gate. Both control planes hold zero broker state, queried today:
51bebbfa-…8a3458ff-…Canary also reports no machine carrying a
broker_box_id. No broker disk holdsa member's credential, so there is nothing to drain and nothing to audit. The
product owner confirms there are no live boxes, so no boot-time repair ships
either.
packages/brokeris deleted, all 44 files.registerandwatchs6 services are deleted. Their nine dependents noworder behind
init-state, which is whereregistersat in the graph.blitz-register,blitz-cred-claudeandblitz-cred-codexare deleted.claudeshim execs the native CLI and mints nothing.blitz-codex-sessionkeeps the Codex shim and loses every broker probe.blitz-cred register.core/registry.tsand its four routes are deleted, with the broker wiretypes,
BoxIdentity.isBroker, and the broker key cleanup in the destroy andorphan janitors.
/boxes/:id/feedand the constant workspace environment route go with them.Both were compatibility surfaces for exactly one caller, and that caller was
the broker.
broker_keys,broker_membersandbroker_boxes, andrebuilds
machinesandboxeswithout the columns that referenced them.credential-helperjob runs the new module's Go tests in their place.
blitz-cred api-tokensurvives. It moves topackages/box/credential-helperwith the primitives it reaches, and it keeps its name, because agent rules and
the Git credential helper already call it by that name. Claude and Codex read
their own stores under HOME.
The risk trade
Migration 0053 is the point of no return. SQLite cannot drop a column that
carries a foreign key, so the migration rebuilds
machines,boxes, the tokenfamilies, the credential leases and
credential_events. Every row count ittouches is zero in both deployments today, which is what makes the rebuild
cheap. It is still a schema change: take the ordinary database backup first.
A box that still carries
model_provider = "blitz"is not repaired. Theplan asked for a boot migration that strips the marked region. The product
owner states there are no live boxes, so that code is not written. A box found
carrying the block is fixed by editing the file, not by a release.
The rejected alternative was the plan's own two-release split. It exists to
protect enrolled brokers and deposited credentials. Both counts are zero, so the
drain window would have protected nothing and delayed the fix that closes the
incident.
packages/brokeralso held the machine bearer. Deleting the packageoutright would have taken
/agent/*authentication with it. That is why thehelper moves rather than dies, and why its tests move with it.
Tests
The moved code keeps a test for each property the plan named:
plane is unreachable;
credential file;
single-use refresh token;
api-tokenprints the token and one newline, and nothing else.packages/box/credential-helper: 4 packages, all pass. Two of them —atomicfileandfilelock— carry the ownership and cross-process lock teststhe move had dropped.
test/broker-retirement-migration.test.tsapplies every migration to an emptydatabase, applies migrations through 0052 to a second database, seeds a broker
box, a broker member, a broker key, an assigned machine, a device-code box,
token families, leases and an audit event, then applies 0053. It compares the
two
sqlite_masterresults, checks the surviving rows, and runsPRAGMA foreign_key_check.That test had a hole, and it is worth naming. It asserted absence with
.not.toEqual(expect.arrayContaining([a, b, c])), which matches only when everyname is present — so it passed as soon as ONE of the three tables was gone, and
two could have survived unseen. Deleting the
broker_boxesdrop from themigration did not fail it. The schema-equality check could not catch that
either: both databases run 0053, so a migration that drops nothing leaves them
equally wrong and still equal. It now asserts each table separately and fails
with
broker_boxes survived the retirement migration.agent-shims.test.tsassertednot.toContain("CLAUDE_CODE_OAUTH_TOKEN")against the whole shim file, so a comment explaining that the shim no longer
touches that token failed the test. It now strips comments and asserts on code.
Repository gates ran in a Linux container from a clone of this branch, which is
what CI runs (
node:22.20.0-bookworm,npm ci,BLITZDEV_MANAGED=1 CI=true):npm run typecheckpasses.npm run lint:gatepasses at 42 anti-slop findings, one below the previousbaseline.
lint-baseline.jsonis lowered in this change, never raised.BLITZDEV_MANAGED=1 CI=true npm testpasses, exit 0. Control plane64 passed (64), box guest tests28 passed (28), webapp118 passed | 12 skipped (130), and everynode --testsuite reports# fail 0.BLITZDEV_MANAGED=1is not optional here. Deleting acore/file needs fourhand-written edits in three files, and the third copy of the manifest lives in
blitzdev-emitter.test.ts, which only runs under that flag. A plainnpm testpassed while the managed build was still 100 files against the worker's 99.
The Go module is not built in that image. CI's
credential-helperjob runsgo buildandgo testfor it on Linux; both pass locally on darwin/arm64.Deploy
Two artifacts ship this, and one database changes.
The control plane ships in the Worker bundle. The push to
mainruns.github/workflows/canary.yml, which applies migration 0053 and deploys.The box ships in the payload and the image.
packages/box/rootfsand theDockerfile both change, so the image job derives a new base image rather than
reusing one. A running box converges on its own:
blitz-payloadpolls every300 s.
Client prod takes the box half at its next
v*image release.Rollback of the code:
Rollback of the schema is not automatic. Migration 0053 drops tables, so
restoring a control plane that expects them needs the database backup taken
before the deploy. Treat every failure after 0053 applies as roll-forward.
🤖 Generated with Claude Code
https://claude.ai/code/session_01GocywGPN9b8DrcBXR9C4id