LearnNewsExamplesServices
Frontmatter
id13244
titleGitHub Workflow healthcheck: detect identity drift before any write
stateClosed
labels
bugdeveloper-experienceaimodel-experience
assigneesneo-opus-ada
createdAtJun 14, 2026, 6:57 PM
updatedAtJun 15, 2026, 1:29 AM
githubUrlhttps://github.com/neomjs/neo/issues/13244
authorneo-opus-ada
commentsCount0
parentIssue13012
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[x] 13243 Fail closed on GitHub write identity drift
blocking[]
closedAtJun 15, 2026, 1:29 AM

GitHub Workflow healthcheck: detect identity drift before any write

Closed v13.1.0/archive-v13-1-0-chunk-2 bugdeveloper-experienceaimodel-experience
neo-opus-ada
neo-opus-ada commented on Jun 14, 2026, 6:57 PM

Context

The 2026-06-14 agent identity drift (incident record #13239 / merged #13240) was a silent failure: a drifted GH_TOKEN made gh api user resolve to the wrong agent, so PRs were opened + reviews posted under a mis-attributed identity — and nothing surfaced it. The github-workflow healthcheck verifies gh auth status (token present + valid) but never that the authed identity is the expected agent, so a drifted-but-valid token passes the healthcheck clean. The drift was only caught by humans reading PR-body self-ids after the fact.

This is the robust auto-detection half of the prevention. The discipline rule (Codex worktree placement #13242) and the cross-family gate-hardening (#13234 → merged #13237) are in; the loud machine detection is not. Per the #13240 prevention section: "a wrong identity should not silently produce a valid-looking PR."

The Problem

The same failure class as #12450 (the Chroma query swallow): a real failure erased into a clean-looking result instead of surfaced. The healthcheck is the natural detection seam — it runs before work — but it currently asserts only token validity, not token identity. A degraded healthcheck on identity-mismatch catches a drifted token before any state-changing write, turning a silent multi-PR misattribution into a loud, single, pre-write halt.

The "expected agent" is knowable: the harness boots with NEO_AGENT_IDENTITY (e.g. @neo-gpt), and ai/graph/identityRoots.mjs IDENTITIES maps each identity id → its canonical properties.githubLogin. So the assertion is deterministic: gh api user --jq .login (the live authed login) must equal the expected identity's githubLogin, and the Memory-Core self-identity (the second surface that shares the drift) must equal it too.

The Architectural Reality

  • The github-workflow healthcheck lives in ai/services/github-workflow/HealthService.mjs (verifies gh auth status today).
  • The expected-identity source is ai/graph/identityRoots.mjs IDENTITIES — each AgentIdentity has id (@neo-...) + properties.githubLogin (@neo-...) + modelFamily. gh api user --jq .login returns the bare login (no @), so the comparison must normalize the @ prefix.
  • The same identity-assertion logic is needed by the write-boundary guard (#13243, @neo-gpt, in ai/mcp/server/github-workflow/toolService.mjs) — so it must be a single shared pure core, not duplicated per-site.

The Fix

  1. Pure shared coreassertExpectedIdentity({expected, actualLogin, memoryCoreIdentity}) -> {ok, reason}: normalizes the @/bare forms, looks up expected's githubLogin in IDENTITIES, and returns {ok:false, reason:"identity drift: authed as X, expected Y"} on any mismatch (fail-closed on a missing/unmappable expected id). Deps injected (the actual login + the resolved identity table) -> unit-provable with no live gh / Memory-Core. Both this healthcheck and #13243's write-guard consume it (one source of truth). Exact file location validated via structural-pre-flight at build time (proposed: a shared ai/ identity util within both consumers' import reach).
  2. Healthcheck integrationHealthService reads NEO_AGENT_IDENTITY + gh api user --jq .login + the Memory-Core self-identity, calls assertExpectedIdentity, and on !ok sets the healthcheck status to degraded with the explicit drift reason. Catches a drifted token before any write.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
assertExpectedIdentity({expected, actualLogin, memoryCoreIdentity}) (new pure core) this ticket returns {ok, reason}; fail-closed on mismatch / unmappable n/a (new surface) JSDoc Anchor & Echo unit spec
HealthService healthcheck status ai/services/github-workflow/HealthService.mjs adds degraded on identity drift (was: only gh auth status) existing auth-status checks unchanged JSDoc unit + degraded-path test
NEO_AGENT_IDENTITY (env) -> expected githubLogin ai/graph/identityRoots.mjs IDENTITIES read-only lookup fail-closed if id unmappable identityRoots JSDoc unit

Decision Record impact

aligned-with the §6.1 cross-family identity model + the #13234 / #13237 gate-hardening — this is the detection counterpart (catches drift at the healthcheck; the gate hardens review eligibility). No ADR change. The canonical identity source is identityRoots.mjs.

Acceptance Criteria

  • assertExpectedIdentity pure core exists with unit coverage: match -> {ok:true}; login mismatch -> {ok:false, reason}; Memory-Core-identity mismatch -> {ok:false}; missing / unmappable expected -> fail-closed {ok:false}; @/bare normalization covered.
  • HealthService healthcheck returns degraded + the explicit drift reason when the authed login OR the Memory-Core identity != the expected agent, and stays healthy when both match.
  • The core is consumed by both this healthcheck and #13243's write-boundary guard (one source of truth — verified by #13243 importing it, not re-implementing).
  • Evidence: a unit test proving the degraded path fires on a simulated drift (the #12450-class regression guard — the failure is surfaced, not swallowed).

Out of Scope

  • The write-boundary fail-closed guard itself (#13243, @neo-gpt — the sibling consuming this core).
  • The root github-workflow MCP @me mis-map (the upstream cause; this hardens detection regardless of that fix).
  • The cross-harness GH_TOKEN isolation discipline (#13242 Codex; the Claude / Gemini equivalents are a separate follow-up).
  • Auto-remediation (resetting the token) — this ticket DETECTS + surfaces; the repair is operator / harness-restart per the #13240 runbook.

Related

  • Sibling: #13243 (@neo-gpt — the write-boundary guard consuming this core).
  • Incident: #13239 / merged #13240 (the runbook; this is its prevention-section "detection seam"). Gate-hardening: #13234 (closed) -> #13237 (merged). Discipline: #13242 (merged).
  • Same failure class: #12450 (silent failure swallowed into a clean result).
  • Parent epic: #13012 (Agent Harness).

Release classification: post-release (Agent-harness integrity; not v13.x-blocking).

Live latest-open sweep: checked the latest 20 open issues + the A2A in-flight claim queue immediately before filing; #13243 is the sibling write-boundary part (not a dup), no equivalent healthcheck-assertion ticket or claim found.

Origin Session ID: 2d993feb-ea2f-4468-8fbd-c53e62365f4d

Retrieval Hint: "github-workflow healthcheck identity assertion drift detection assertExpectedIdentity NEO_AGENT_IDENTITY identityRoots"