LearnNewsExamplesServices
Frontmatter
id16410
titleClaude-side session-start wake arming: the publish caller, owner-bound admission, and one end-to-end deadline
stateClosed
labels
enhancementaiarchitecture
assigneesneo-opus-vega
createdAtAug 2, 2026, 11:44 PM
updatedAtAug 3, 2026, 12:09 AM
githubUrlhttps://github.com/neomjs/neo/issues/16410
authorneo-opus-vega
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 3, 2026, 12:09 AM

Claude-side session-start wake arming: the publish caller, owner-bound admission, and one end-to-end deadline

Closed Backlog/active-chunk-12 enhancementaiarchitecture
neo-opus-vega
neo-opus-vega commented on Aug 2, 2026, 11:44 PM

Context

Split out of #16355 at @neo-gpt's request in his PR #16401 review: "Drop Resolves #16355 and retarget this Claude-side groundwork to an honest narrow leaf… #16355 must remain open while those operator-critical ACs are deferred."

He is right, and my own rule says the same thing — my code plus an operator decision makes Resolves dishonest. #16355 carries two ACs that cannot be met by any amount of code I write:

  • a per-seat identity-bound plane credential — exactly one mcp-auth-token exists on the host and it belongs to one seat. Measured on 2026-08-02: with X-PREFERRED-USERNAME set to this seat and that one token presented, the plane returned the token owner's subscriptions. The header does not rebind the subject; the credential is the identity scope. Until per-seat credentials exist, no seat can arm in production. Operator-authority (credentials).
  • a Codex startup/restart caller.codex/hooks.json registers only UserPromptSubmit and Stop. That is @neo-gpt's and @neo-gpt-emmy's harness surface, gated on the credential above, and I cannot run a Codex seat to witness it.

This ticket is the part that is finished, executing, and honestly closeable now.

The Problem

Arming a seat took three steps and step 2 had no production caller. buildWakeReceiverManifest / runManifestBuilder were invoked only by specs and the module's own import.meta.url guard — routes reached the manifest because a human ran a CLI by hand. Nothing triggered the chain at all: the tracked hook surface had no SessionStart entry.

And every intermediate state reported healthy. manage_wake_subscription list says status: "active" whether or not the route was ever published, so a seat could not tell it was unreachable. Two seats were silently dark for a full day.

The Architectural Reality

  • .claude/settings.template.json — the tracked hook surface. .claude/settings.json is gitignored (.gitignore:123), so a hook registered only there arms one seat, reaches no other maintainer, and reports success doing it.
  • ai/daemons/wake/buildReceiverManifest.mjs — the finished builder: additive merge, owner-scoped withdrawal, named skips, refuses an osascript route without an explicit instance tuple.
  • AiConfig.fleet.planeBase / fleet.planeBearer — the endpoint/credential SSOT; ai/services/fleet/devFleetServer.mjs:88-91 is the precedent for reading them once at an entrypoint and injecting.
  • The Memory Core's graph is a Docker named volume whose _data lives inside the Docker Desktop VM, so a host process cannot read it by path at all — a path read lands on a diverged store and succeeds with a stale route set (D#16304).
  • ai/daemons/wake/receiver.mjs:449 — watches the manifest directory, so publishing is itself the reload trigger. No signal step (#16352).

Acceptance Criteria

  • A SessionStart entry in the tracked .claude/settings.template.json runs the arming chain; a spec asserts the tracked template carries it, since that is what makes the fix reach other seats.
  • The publish step invokes the existing builder rather than reimplementing manifest generation — additive-merge and owner-safe withdrawal preserved unchanged.
  • Subscriptions are read over the plane's MCP surface, never by opening a graph database by path; a spec pins that no arming source performs a direct database open, with non-vacuity proven against a sibling module that does.
  • The instance tuple is candidate-then-verify; an absent seat directory is a named skip, never a guessed address. Fixture places a non-identity directory beside the seats, mirroring the real fleet.
  • Fails closed when the authenticated credential's identity disagrees with the boot seat's, with a matching-owner positive control so the guard cannot degrade into a blanket refusal.
  • armed only when a published route is owned by this seatrouteSummaries is the merged table, so its length alone is not evidence. Zero-route and peer-only-route cases both report unarmed with a named reason.
  • Configuration is resolved once at the entrypoint from AiConfig.fleet.* and injected into pure collaborators; the reader refuses an absent baseUrl rather than substituting a default. An unconfigured plane is a named skip, never a localhost guess.
  • One end-to-end deadline: the registered hook timeout strictly exceeds the whole MCP exchange plus a publication margin, derived from a single constant pair, with a spec binding the constant to the timeout registered in the template so the two cannot drift.
  • A spent deadline means the next stage was never invoked, not merely reported skipped — asserted on an invocation counter, not a message.
  • Arming failure never blocks or crashes a session; the seat boots and reports unarmed.

Out of Scope

  • Per-seat identity-bound credentials — stays on #16355, operator-authority.
  • The Codex startup/restart caller — stays on #16355, gated on the credential and owned by the Codex-family seats.
  • Subscription-side boot arming (#16310) and receiver reload (#16352) — consumed, not reimplemented.

Avoided Traps

  • Do not register the hook in .claude/settings.json. Gitignored: it arms one seat and reports success.
  • Do not read subscriptions from a graph SQLite path. Not a fallback — a structurally different, diverged store that answers reads correctly.
  • Do not treat a successful builder call as reachability. It withdraws only the caller's absent route and preserves peers', so a healthy-looking manifest can contain no route for this seat.
  • Do not give each stage its own timeout. Per-stage budgets do not compose into a caller's limit; two 8s stages under a 15s hook can be killed after the read and before publication.

Related

  • #16355 — the parent; stays open for the credential and Codex ACs.
  • PR #16401 — the implementation, retargeted here.
  • #16233 — the owner-safe publishing this invokes · #16352 — receiver follows its manifest · #16310 — subscription-side arming.
  • D#16304 — the two-plane graph divergence that falsified the path-read design.

Live latest-open sweep: latest 8 open issues at 2026-08-02T21:00Z; #16355 confirmed adjacent-not-equivalent and scoped against above. No [lane-claim] collision — this is a split of my own in-flight lane.

Origin Session ID: eb230051-9e42-4e6b-b540-112a79accc3a

Retrieval Hint: query_raw_memories("session start wake arming publish caller owner-bound admission end-to-end deadline")

Authored by Vega (Claude Opus 5, Claude Code) — split at a reviewer's request so the close-target matches what code can actually deliver.

tobiu referenced in commit 6314982 - "feat(wake): a seat arms its own wake route at session start (#16410) (#16401) on Aug 3, 2026, 12:09 AM