fix: derive the login scope set from ALL_SCOPES instead of hand-listing it - #303
Merged
Conversation
…ng it
`codeoid login` asked ZeroID to mint a hand-maintained literal list of scopes
that had drifted from the canonical set in BOTH directions:
never minted (7) settings:read, settings:write, fleet:read,
pipeline:create, pipeline:read, pipeline:answer,
pipeline:manage
not real (4) tools:read, tools:write, tools:execute, tools:agent
codeoid enforces token scopes verbatim (auth.ts: `scopes: identity.scopes ??
[]`) — there is no server-side expansion or default — so a scope absent from
the mint request is unreachable for the life of the key. A login token
therefore could not open the settings screen, the fleet board, or any pipeline
verb.
The failure surfaces far from its cause: a bare "Missing scope: settings:read"
from a daemon whose config is otherwise correct. Nothing an operator can do
fixes it — not re-login, not restarting the daemon, not upgrading ZeroID
(which never sees these scopes as anything but opaque strings; its own scope
ceilings were empty and it grants what is requested).
`ALL_SCOPES` is the single source of truth. `ALL_SCOPES_STRING` is the same
join the terminal and TUI clients already use; only the login path hand-rolled
its own. The new drift test asserts the derivation structurally, so adding a
scope can no longer leave this behind.
This is an owner-tier token — it now includes `settings:write` and
`pipeline:manage`. That matches the single-operator daemon codeoid targets:
the key is minted by, and for, whoever runs it. Delegated access is expressed
by exchanging this token DOWN (as the conductor and watcher paths already do),
not by under-minting it here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jalbrethsen-highflame
approved these changes
Aug 25, 2026
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.
The bug
codeoid loginasked ZeroID to mint a hand-maintained literal scope list (src/cli.ts) that had drifted fromALL_SCOPESin both directions:Never minted — real scopes the daemon enforces:
Not real scopes — requested but never defined in
SCOPES:codeoid enforces token scopes verbatim —
auth.ts:scopes: (identity.scopes ?? []) as Scope[], with no server-side expansion or default. So a scope missing from the mint request is unreachable for the life of the key. A login token structurally could not open the settings screen, the fleet board, or any pipeline verb.Why it was hard to diagnose
The failure surfaces far from its cause — a bare
Missing scope: settings:readfrom a daemon whose config is otherwise fine. Nothing an operator can do fixes it:credential_policies.allowed_scopesandservice_keys.scopeswere both empty (no ceiling),oauth_tokenshad zero rows (stateless JWTs, nothing cached), and no codeoid scope string appears in the ZeroID binary at all. It grants what it's asked for — it was simply never asked.The comment above the list read "The full scope set codeoid asks ZeroID to mint", which is precisely the claim that rotted when
settings:*was added infc149bd(2026-07-16).The fix
ALL_SCOPESis the single source of truth.ALL_SCOPES_STRINGis the same jointerminal/client.tsandtui/ws.tsalready use — only the login path hand-rolled its own.Owner-tier question, flagged not buried
This makes the login token owner-tier: it now includes
settings:writeandpipeline:manage, whichscopes.tsexplicitly calls out as owner-tier ("the same trust class assettings:write").I think that's right for the single-operator daemon codeoid targets — the key is minted by, and for, whoever runs it, and delegated access is already expressed by exchanging this token down (the conductor and watcher paths do exactly that) rather than by under-minting here. But it is a deliberate widening and worth a second opinion. The alternative is a named
OWNER_SCOPESconstant that's still derived, just narrower.Verification
lint+typecheckclean, 2372 pass / 0 fail. Four new drift assertions: the derivation is checked structurally (so re-introducing a literal array fails), plus no-missing, no-unknown, and an explicit check on the seven scopes whose absence caused the original report.After merge, one
codeoid login <key>re-mint is needed for existing keys.🤖 Generated with Claude Code