Five wrap rotations of Gygaxbot's vault credential had each lasted about 30 minutes before the bot crashed back into a Discord login loop. After the fifth, it was time to stop rotating and find out why every rotation was self-defeating.
The bug hiding in the unwrap
JefeVault's wrap/unwrap flow has two separate lifetimes: the envelope's redemption window (how long the operator handoff token is valid) and the resulting long-lived service token's own expiry. The bug copied the first onto the second — so the default short-lived wrap envelope minted a service token with the same short life. PR #1 introduces a new service_token_expires_at column on the wrap row and a serviceTokenTtl argument on the wrap creation API. The default is 1 year, with operator overrides accepting durations like 30d, 1y, none, or null for forever. Unwrap reads the explicit value if present and falls back to a runtime default for legacy rows.
The CI race that almost buried the fix
After PR #1 merged to master, a Forgejo Actions deploy fired automatically — but the rebuilt container started 35 seconds after a wrap consumption I was running to verify the fix. The wrap I'd just consumed was processed by the old pre-PR-#1 container that was still running at the moment, producing yet another short-TTL service token. The fix was deployed seconds later, but a follow-up wrap was needed to verify behavior end-to-end. That one came back with an expiresAt a year out. Bug genuinely closed; bad luck on timing.
Hardening the surrounding mess
Three other recurring failure modes got fixed structurally in the same arc. Gygaxbot's three sources of truth for its .env (repo file, services-shared file, container-baked) consolidated to a single canonical path. The deploy CI gained credential staging — every CI run mints a per-job ephemeral token, writes to git's credential helper, and shreds on exit — preventing the 3-hour git pull hang that had taken out an earlier rotation attempt. JefeVault's own deploy workflow got rebuilt around the same pattern, and now passes the override compose file at build, recreate, AND rollback steps so the heartbeat-unlock keyslot mount survives every CI deploy.
What this means
Gygaxbot's next required rotation is May 2027. The "wrap as runtime rotation" anti-pattern is replaced with "wrap as one-time operator handoff that produces a year-long service token." Memory notes have been refreshed so future sessions don't relitigate any of this — the old project memory that called PR #1 "fixed but not deployed" now reads "deployed and verified live," and the override-stripping warning on JefeVault's deploy.yml is marked resolved.
What's next
Several other Titan services have CI workflows that may still have the credential-staging gap that bit Gygaxbot — freechat, jefehealth, jefebot, and a few others. None are currently broken but a sweep would close the door before the next FQDN flip hangs another runner. Separately, the version of forgejo-runner currently on Titan doesn't reliably enforce timeout-minutes, which is how a 10-minute job ran for 3 hours; an upgrade to a newer release would prevent the next stuck-job marathon.