Confirmed production bug.install.sh v1 §5 keyed idempotency on configs/credentials.json only. Re-running on a pending-then-approved node (which the script's own output tells you to do) found no credentials.json, fell through to a fresh POST /nodes/enroll, and hit enrollment.py's already-approved 403 → rendered as "use Reissue key". The working path — GET /nodes/{id}/credentialswith the savedpickup_secret` — was only tried within a single run.
§5 is now a decision tree run before any POST /nodes/enroll:
state
action
credentials.json has api_key
skip (unchanged)
configs/pickup_secret exists
GET /credentials: 200+key → write & done; 200 no key → "approve it, re-run", clean exit; 401 → re-enroll iff --token else die; 404 → same; 000 → connectivity die
neither
fresh enroll (do_fresh_enroll)
Also: fresh-enroll 403/401/429 handlers are specific and point at pickup-secret recovery; --wait-approval polling now applies on re-run; §7 banner conditional on pickup_secret.
Verified against enrollment.py @ v1. approve_node mints node_keys/{id}.api_key synchronously — no second bug found; system assignment is independent and not required for a key. bash -n clean; not run on a Pi from this env.
After merge: move v1 to this commit (git tag -f v1 <sha> && git push -f origin v1) — DEFAULT_REF and the mint-panel URL both point at v1.
Recovery for a node already stuck (approved, no credentials.json, pickup_secret on disk): re-run the patched install.sh with no --token, or the curl … /credentials -H X-Pickup-Secret one-liner.
**Confirmed production bug.** `install.sh` v1 §5 keyed idempotency on `configs/credentials.json` only. Re-running on a pending-then-approved node (which the script`'s own output tells you to do) found no `credentials.json`, fell through to a fresh `POST /nodes/enroll`, and hit `enrollment.py`'s already-approved 403 → rendered as "use Reissue key". The working path — `GET /nodes/{id}/credentials` with the saved `pickup_secret` — was only tried within a single run.
§5 is now a decision tree run **before** any `POST /nodes/enroll`:
| state | action |
|---|---|
| `credentials.json` has `api_key` | skip (unchanged) |
| `configs/pickup_secret` exists | `GET /credentials`: 200+key → write & done; 200 no key → "approve it, re-run", clean exit; 401 → re-enroll iff `--token` else die; 404 → same; 000 → connectivity die |
| neither | fresh enroll (`do_fresh_enroll`) |
Also: fresh-enroll 403/401/429 handlers are specific and point at pickup-secret recovery; `--wait-approval` polling now applies on re-run; §7 banner conditional on `pickup_secret`.
Verified against `enrollment.py` @ `v1`. `approve_node` mints `node_keys/{id}.api_key` synchronously — no second bug found; system assignment is independent and not required for a key. `bash -n` clean; not run on a Pi from this env.
**After merge:** move `v1` to this commit (`git tag -f v1 <sha> && git push -f origin v1`) — `DEFAULT_REF` and the mint-panel URL both point at `v1`.
**Recovery for a node already stuck** (approved, no `credentials.json`, `pickup_secret` on disk): re-run the patched `install.sh` with no `--token`, or the `curl … /credentials -H X-Pickup-Secret` one-liner.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Confirmed prod bug: §5 keyed idempotency on configs/credentials.json only.
Re-running the installer on a pending-then-approved node (the exact flow the
script's own output tells you to do) found no credentials.json and fell
through to a fresh POST /nodes/enroll — which enrollment.py's CRITICAL GUARD
answers with 403 for an already-approved node_id, rendered as "use Reissue
key". The working path (GET /nodes/{id}/credentials with the saved
pickup_secret — no already-approved guard on that endpoint) was only ever
tried within a single run.
§5 rewritten as a decision tree that runs before any POST /nodes/enroll:
- credentials.json has api_key -> skip (unchanged)
- configs/pickup_secret exists -> GET /credentials with it:
200 + api_key -> write credentials.json, done
200, no key -> say "approve it, re-run"; clean exit, start stack
401 (rotated) -> re-enroll iff --token, else specific die
404 (deleted) -> re-enroll iff --token, else specific die
000 -> connectivity die
- no creds, no pickup_secret -> fresh enroll (do_fresh_enroll)
Also: fresh-enroll 403/401/429 handlers are now specific and point at
pickup-secret recovery, not just "Reissue key"; --wait-approval polling now
applies on the re-run path; §7's "re-run the installer" banner is
conditional on pickup_secret existing.
Response shapes verified against enrollment.py @ v1. approve_node mints
node_keys/{id}.api_key synchronously — no second bug; assigning a system is
independent and not required for a key. bash -n clean.
Recovery for a node stuck by the old behaviour (approved, no credentials.json,
pickup_secret on disk): re-run the patched install.sh (no --token needed), or
curl -fsS $C2_URL/nodes/$NODE_ID/credentials \
-H "X-Pickup-Secret: $(cat configs/pickup_secret)" | jq '{api_key}' > configs/credentials.json
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
logan
merged commit 31e1176c45 into main2026-09-06 22:32:19 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Confirmed production bug.
install.shv1 §5 keyed idempotency onconfigs/credentials.jsononly. Re-running on a pending-then-approved node (which the script's own output tells you to do) found nocredentials.json, fell through to a freshPOST /nodes/enroll, and hitenrollment.py's already-approved 403 → rendered as "use Reissue key". The working path —GET /nodes/{id}/credentialswith the savedpickup_secret` — was only tried within a single run.§5 is now a decision tree run before any
POST /nodes/enroll:credentials.jsonhasapi_keyconfigs/pickup_secretexistsGET /credentials: 200+key → write & done; 200 no key → "approve it, re-run", clean exit; 401 → re-enroll iff--tokenelse die; 404 → same; 000 → connectivity diedo_fresh_enroll)Also: fresh-enroll 403/401/429 handlers are specific and point at pickup-secret recovery;
--wait-approvalpolling now applies on re-run; §7 banner conditional onpickup_secret.Verified against
enrollment.py@v1.approve_nodemintsnode_keys/{id}.api_keysynchronously — no second bug found; system assignment is independent and not required for a key.bash -nclean; not run on a Pi from this env.After merge: move
v1to this commit (git tag -f v1 <sha> && git push -f origin v1) —DEFAULT_REFand the mint-panel URL both point atv1.Recovery for a node already stuck (approved, no
credentials.json,pickup_secreton disk): re-run the patchedinstall.shwith no--token, or thecurl … /credentials -H X-Pickup-Secretone-liner.🤖 Generated with Claude Code
Confirmed prod bug: §5 keyed idempotency on configs/credentials.json only. Re-running the installer on a pending-then-approved node (the exact flow the script's own output tells you to do) found no credentials.json and fell through to a fresh POST /nodes/enroll — which enrollment.py's CRITICAL GUARD answers with 403 for an already-approved node_id, rendered as "use Reissue key". The working path (GET /nodes/{id}/credentials with the saved pickup_secret — no already-approved guard on that endpoint) was only ever tried within a single run. §5 rewritten as a decision tree that runs before any POST /nodes/enroll: - credentials.json has api_key -> skip (unchanged) - configs/pickup_secret exists -> GET /credentials with it: 200 + api_key -> write credentials.json, done 200, no key -> say "approve it, re-run"; clean exit, start stack 401 (rotated) -> re-enroll iff --token, else specific die 404 (deleted) -> re-enroll iff --token, else specific die 000 -> connectivity die - no creds, no pickup_secret -> fresh enroll (do_fresh_enroll) Also: fresh-enroll 403/401/429 handlers are now specific and point at pickup-secret recovery, not just "Reissue key"; --wait-approval polling now applies on the re-run path; §7's "re-run the installer" banner is conditional on pickup_secret existing. Response shapes verified against enrollment.py @ v1. approve_node mints node_keys/{id}.api_key synchronously — no second bug; assigning a system is independent and not required for a key. bash -n clean. Recovery for a node stuck by the old behaviour (approved, no credentials.json, pickup_secret on disk): re-run the patched install.sh (no --token needed), or curl -fsS $C2_URL/nodes/$NODE_ID/credentials \ -H "X-Pickup-Secret: $(cat configs/pickup_secret)" | jq '{api_key}' > configs/credentials.json Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>