Repository navigation
Ergonomics: provisioning a headless signing agent is a ~9-step ceremony — make it one command #382
Description
Context
Setting up a delegated agent that can sign commits/releases headlessly (no fingerprint, for CI or an autonomous assistant) is the product's flagship use case — "one budget, N agents, provable" applies just as much to signing as to spending. But provisioning one today is a long, undocumented ceremony that mixes CLI commands with manual git surgery on the registry. This session got there and it verified end to end (agent-signed commit accepted through the pinned root, PR #381), but only after ~9 steps and discovering several bugs along the way.
This issue is purely about ergonomics: collapse the ceremony into a single command. (The correctness bugs found en route are fixed separately in #381; residual bugs are listed at the bottom as blockers.)
Current path (what it actually takes today)
Delegating claude-release under an SE root and getting it to sign a verifiable commit required:
auths id agent add --label claude-release --key main --curve ed25519 --scope sign_commit --scope sign_release --expires-in 2592000— fingerprint the delegation. (Must setAUTHS_PASSPHRASEto a 12+ char value first, or the grandfathered cached passphrase is silently reused and rejected by the length policy.)auths key copy-backend --alias claude-release --dst-backend file --dst-file <keys.enc>— move the agent key into a file keychain.auths key copy-backend --alias claude-release--next-0 ...— again for the pre-committed next key (easy to forget; signing half-works without it).- Hand the passphrase to the headless environment (write it to a file the agent reads).
- Materialize an agent delegate-machine registry —
cp -R ~/.auths <agent-registry>, then rewriterefs/auths/registryviagit read-tree/rm --cached/write-tree/commit-tree/update-refto drop every identity subtree except the agent'sdip. This is raw git plumbing no user should ever run, and it's undocumented. - Stamp
<agent-registry>/commit-trailerswith the agent'sAuths-Id/Auths-Device/Auths-Anchor-Seq(the seq read out of the root's nestedstate.json). - Regenerate
.auths/ci-bundle.jsonso CI's bundle evidence carries the new delegation anchors. - Ensure the repo's traveling
refs/auths/registryis pushed so verifiers can resolve the delegation. - Sign — with a pile of env + config overrides that are non-obvious:
(The
export AUTHS_KEYCHAIN_BACKEND=file AUTHS_KEYCHAIN_FILE=<keys.enc> AUTHS_PASSPHRASE=<pass> export AUTHS_HOME=<agent-registry> AUTHS_REPO=<agent-registry> GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=user.signingkey GIT_CONFIG_VALUE_0=auths:claude-release \ auths sign HEAD --scope sign_commit env -u AUTHS_HOME -u AUTHS_REPO auths verify HEAD
GIT_CONFIGtrick is required becauseauths sign's amend reads the repo'suser.signingkey, not the flag/env, so it fails "Key not found" without it.)
Nine steps, two of which (5, 6) are git plumbing and manual JSON spelunking. Nobody will do this correctly unaided.
What "done" looks like — one command
auths agent provision --label claude-release \
--scope sign_commit,sign_release --expires-in 30d \
--backend file --out ~/.auths-claude...prints (and writes to ~/.auths-claude/env.sh):
✓ Delegated claude-release (did:keri:EO1cBsYo…) under did:keri:EB5cPHY0… — scopes: sign_commit, sign_release; expires 2026-08-17
✓ Agent keychain: ~/.auths-claude/keys.enc (file backend)
✓ Agent registry: ~/.auths-claude/registry (delegate machine)
✓ CI bundle refreshed, traveling ref synced
To sign as this agent:
source ~/.auths-claude/env.sh
auths sign <ref> # scope inferred; --scope to override
Revoke anytime:
auths id agent revoke did:keri:EO1cBsYo… --key main
And signing itself becomes just:
source ~/.auths-claude/env.sh
auths sign HEADPlan
A. New auths agent provision command that internally runs steps 1–8 as one atomic operation:
- Delegate (step 1), auto-copying both current + next keys into the chosen backend (steps 2–3) — no separate
copy-backendcalls, no "forgot the next key". - Own the delegate-machine materialization (step 5) in SDK code, not user git plumbing.
Chain-style machine construction already exists inauths-mcp-gateway/src/chain.rs(materialize_agent_machine) — promote that intoauths-sdkand reuse it here. Users should never touchrefs/auths/registryby hand. - Write the
commit-trailersfile (step 6) from resolved KEL state, not hand-copied JSON. - Regenerate the CI bundle + confirm the traveling ref (steps 7–8) as part of provisioning, with a clear note when a push is needed.
- Emit a single
env.shcapturing every var, and (optionally) the passphrase handling (--passphrase-file, or generate + store one).
B. Make auths sign respect its own signer selection so step 9's GIT_CONFIG_COUNT hack dies: auths sign --signer auths:<alias> (or the AUTHS_SIGNING_KEY env) should drive the amend directly instead of falling back to repo user.signingkey.
C. Provisioning presets for the two dominant shapes:
--profile ci→ file backend, env.sh, bundle refresh, print the exact CI secret/env block.--profile assistant→ same but oriented at a long-lived local headless agent (what this session set up).
D. Docs: one page, "Delegate a headless signing agent," that is literally the single command + source env.sh. The current 9-step reality should never appear in docs — it should stop existing.
Acceptance
- A fresh delegated agent that can sign a verifiable commit (accepted by
auths verifythrough the pinned root) is created by one command +source env.sh, on both a passphrase-file root and a Secure Enclave root. - No step requires
git read-tree/write-tree/commit-tree/update-ref, hand-editingcommit-trailers, orGIT_CONFIG_COUNToverrides.
Blockers / related
- keychain: Secure Enclave custody fixes — delegation works on SE roots, software keys are never silently discarded #381 fixes the SE custody bugs that make delegation possible on an SE root at all (KEL anchor raw-decrypt;
store_keydiscarding provided material). Provisioning on SE roots depends on it. - Follow-ups this ergonomics work should also absorb:
key exportrefuses per-backend instead of per-key (software keys in an SE backend can't export — worked around viacopy-backend); a mid-ceremony SE auth failure leaves a partially-anchored delegation (keys stored, later seals missing) — provisioning must be atomic or resumable; the passphrase min-length policy silently rejects the cacheddev.auths.passphrasekeychain item.
🤖 Generated with Claude Code
Activity
bordumb commentedon Jul 18, 2026
Field report: drove two full releases headlessly with a provisioned agent
Since filing this, I actually provisioned the
claude-releaseagent and used it to run two complete auths releases (v0.1.7, v0.1.8) with zero fingerprints — bump commit + release tag signed by the agent (sign_releasescope), and CI'sverify-commitsaccepted the agent-signed commits against the pinned root. So the north star ("one command → releases with no fingerprint") is real and proven, not aspirational. That said, daily use surfaced friction beyond the provisioning ceremony thatauths agent provision(or adjacent fixes) should absorb:New pain points, from actually signing with the agent
auths signignores its own signer selection — every sign needs aGIT_CONFIGhack.auths sign <ref> --scope …amends the commit, and the amend reads the repo'suser.signingkey, not a flag/env. Without an override it fails[AUTHS-E5909] keychain unavailable: Key not found. Every single sign in the release flow needed:This is item B above, now with a concrete, repeated failure mode.
auths sign --signer auths:<alias>(orAUTHS_SIGNING_KEY) must drive the amend directly.The
commit-trailersfile is a static cache, not live resolution. Signing stampsAuths-Id/Auths-Devicefrom the literal$AUTHS_HOME/commit-trailersfile. Switching between root-signing and agent-signing (or a stale agent registry) silently stamps the wrong device, and the commit then fails verify withSignerKeyMismatch. Provisioning writes this once; nothing keeps it correct as you switch identities. (This also caused a separate human-facing bug — see identity: signer resolution must not stamp a delegated device it can't sign as #383.)Dual-registry
AUTHS_HOMEjuggling. To sign as the agent you must pointAUTHS_HOME/AUTHS_REPOat the agent's delegate-machine registry (so the resolver finds the agent as signer), but to verify you mustenv -u AUTHS_HOME -u AUTHS_REPOback to the default. Getting this wrong yields confusing verdicts.provision'senv.shshould encapsulate it, andauths verifyshouldn't need the unset dance..auths/ci-bundle.jsonmust be regenerated after every new delegation — and committed. CI verifies agent-signed commits against the committed bundle; a bundle predating the delegation failsRoot never anchored this device's delegated inception. For a release repo this is a required, easy-to-forget stepprovisionshould perform (and re-run on re-delegation).Blocker status update
Unknown encryption tagcrash + thestore_key-discards-material bug are fixed). SE provisioning is unblocked.agent:role). This is a hard requirement forprovision: creating a signing agent must never break the human's ownauths verify. Worth stating explicitly in the acceptance criteria.One addition to the plan
auths agent provisionshould mark the delegation agent-role and rely on #383's rule, so the human's identity is never affected — and it should own theci-bundle.jsonrefresh (point 4) and emit theenv.shthat hides the dual-registry env (point 3). Combined with fix B (point 1), signing collapses tosource env.sh && auths sign <ref>.🤖 Generated with Claude Code
bordumb commentedon Jul 20, 2026
Field report #2: v0.1.13 release — the signing gap is now tags, not just commits
Drove another full headless release (
v0.1.13) with the provisionedclaude-releaseagent. The commit half is solid — but the release surfaced a gap this issue doesn't cover yet, plus a concrete re-confirmation of item B.New: agent-signed tags have no path — and "signed releases" needs them
The release commit signs fine via the documented dance. The tag does not.
git tag -a(what1_github.py --pushand any annotated tag runs) routes throughtag.forceSignAnnotated=true→gpg.ssh.program=…/auths-sign(the SE wrapper) → dies headless:There is no
auths-native way to agent-sign a tag:auths sign --scopeis "Commit-only" (per--help), and artifact signing (--key/--device-key→.auths.json) has no scope and isn't a git object. So the only headless path is to create an unsigned tag and lean on the signed commit for provenance:git -c tag.forceSignAnnotated=false tag -a vX.Y.Z -m "release: release for X.Y.Z"If the product promise is signed releases, item B (signer selection) should extend to tags — e.g.
auths sign refs/tags/vX.Y.Z --scope sign_release, orauths agent provisionshould emit an env that makesgit tag -sresolve to the agent key headlessly. Right now a released tag carries no agent signature.The release scripts aren't agent-aware either
A provisioned
--profile ci/assistantagent still can't use the repo's own release tooling:just releaserunscargo+ assumes the SE fingerprint prompt, and1_github.py --pushhits the tag-signing trap above and only pushes the tag (nevermain). I had to bypass both and hand-roll the tag +mainpush. Whateverprovisionships,--profile cishould also makescripts/releases/*work under the agent — otherwise the "one command → headless releases" story stops one step short of the actual release command. (I rewrotescripts/releases/README.mdinto a working headless runbook in the meantime, but that's a patch over the tooling gap.)Item B, re-confirmed live in v0.1.13
Every
auths signin the release still needed theGIT_CONFIGhack; without it,[AUTHS-E5909] keychain unavailable: Key not found.auths signhas no--signerflag yet (--helpshows only--key/--device-key/--scope/--note), so the amend still falls back to the repo'suser.signingkey:Net: the provisioning ceremony is the headline, but releasing with the provisioned agent still needs (1) a tag-signing story, (2) headless-aware release scripts, and (3) B's
--signer. Suggest folding (1) and (2) into this issue's scope, since a "headless signing agent" that can't sign the release tag or run the release script isn't done.🤖 Field report from a Claude Code session that cut v0.1.13.