Build Log
M5 Repair — Publication Fixed, a Credential Boundary Found and Closed
August 26, 2026
Built and mechanically verified ForgeKit-owned publication for fresh Actor commits, dispatched one real M5 validation that found a genuinely new credential-exchange failure, then fixed and re-verified that gap in the same session rather than deferring a 4th time.
Timeline
What shipped
Resolved a real main-checkout ownership conflict at session start — a prior session's window had closed without SessionEnd releasing its claim; confirmed with Zeb it was genuinely closed, then force-released via the CLI carve-out (not inferred unilaterally)
Added a 'Record pre-run master SHA' step to actor-spawn.yml, capturing the exact starting SHA as a typed step output for later divergence comparison
Added 'Publish completed Actor state' — a deterministic, ForgeKit-owned workflow step (plain shell, zero LLM reasoning) that runs after the Claude action regardless of its own conclusion, detects whether new local commits exist, fetches origin fresh, and only pushes if origin has not moved since the run started; fails closed (no push) on fetch failure, remote divergence, or push failure — never force-pushes, merges, or rebases
Added 'Confirm publication on the authoritative remote' — an independent re-fetch-and-compare step that never trusts the publish step's own self-report
Extracted the actual logic into two standalone, reusable scripts (forgekit-os/scripts/publish-actor-state.sh, confirm-actor-publish.sh) so the workflow and a real regression test share identical code, not a reimplementation
Added checkPublicationStep() to spawn-fresh-actor.mjs, reading the run's own per-step conclusions to report CONFIRMED/FAILED/UNCONFIRMED/SKIPPED/UNKNOWN — a signal genuinely independent of both the Claude action's own conclusion and verifyTerminalArtifact's origin-content check; wired into --wait so publication status prints before the terminal-contract check, matching the Charge's required ordering; added a standalone --check-publication CLI mode
Wrote forgekit-os/scripts/tests/publish-actor-state.test.mjs — 5 tests against real disposable local git fixtures (a bare repo standing in for origin, real clones standing in for the runner checkout and a concurrent pusher) proving: no-new-commits is a clean no-op; a real new commit with no divergence produces a real git push, independently confirmed against the actual bare remote; a genuinely concurrent remote push causes a fail-closed refusal with the remote provably untouched; a push-target failure fails closed rather than reporting false success; the confirmation script itself fails closed on a SHA mismatch
Added .gitattributes (*.sh text eol=lf) after noticing Windows git flagged the new shell scripts for CRLF conversion — confirmed the staged blobs were still clean LF and hardened against a future Windows checkout corrupting them
Ran the full existing 16-file forgekit-os/scripts/tests/*.test.mjs suite — zero regressions
Committed (e13cb1f7) and pushed to origin/master; independently re-fetched and confirmed both the deployed workflow content and the HEAD SHA match
Per the Charge's own instruction, dispatched exactly one fresh M5 validation run (33001453243) against the unmodified frozen Charge and did not execute or poll it further within that turn
At session-close time, found the dispatched run had completed (conclusion: failure) — diagnosed the failure precisely: the Actor did real, correct, tested, honestly-committed work (extracted ignition-core.md §5 KNOWN TRAPS to ignition-traps.md, fixed a real engine-guard.mjs dependency this broke, verified before/after with npm run guard) and the new publish step correctly detected the new commit, correctly attempted the safety-checked push, and correctly failed closed when the push itself hit 'Invalid username or token' — root-caused via the run's own log to the claude-code-action's OIDC-to-app-token exchange ('Using GITHUB_TOKEN from OIDC') replacing the checkout's original persist-credentials header with one that does not authenticate for a later plain-shell git push
Hit the Spin Triage retro gate's hard-fail on deferral strike 3 for the 'OS & Gate Logic — fresh-execution boundary integration' category; rather than self-certifying human_accepted, asked Zeb directly, who chose to resolve rather than accept a 4th deferral
Fixed the credential-exchange gap directly in publish-actor-state.sh: when a GITHUB_TOKEN env var is present and origin is a github.com https remote, explicitly re-installs a fresh 'AUTHORIZATION: basic' HTTP header (the identical shape actions/checkout itself installs) before any fetch/push, rather than trusting whatever credential state a prior step left behind
Wired GITHUB_TOKEN into the workflow's Publish step via an explicit env: block (a run: step does not inherit it automatically) using the standard ${{ github.token }} expression — no new secret
Added 2 new fixture tests: one proving the credential header is actually installed when GITHUB_TOKEN is present and origin looks like github.com, one proving zero side effect when GITHUB_TOKEN is absent (preserving the exact local/test behavior already proven) — 7/7 passing, full 16-file suite re-run with zero regressions
Committed and pushed the credential fix; independently re-fetched origin and re-confirmed the deployed content
“Invalid username or token. Password authentication is not supported for Git operations.”