Repository navigation
Description
Design doc: files/designs/code-mcpx-stdio-exec-identity.md in the keep-the-four-repos-healthy goal workspace. Register: central potential-projects list, same workspace (files/potential-projects.md).
Identity decision (recorded here by the design architect, before implementation): for a hashed executable whose content begins with #!, the exec identity is its content plus the resolved directory of the real file — a script's identity is what it runs, where it runs from. For a non-script hashed file the identity stays content-only, and above limits.execHashMax (1MB) it stays the stat/inode identity. No user/review: the roadmap row sits at proposed and pkgs/mcpx/TODO.md item 4 asks for exactly this decision ("Decide whether the identity should include the program's directory for scripts, or whether a re-pointed link should always restart"); the chosen rule is the TODO's first option, it is a small, reversible semantic change inside the existing identity mechanism, and no new surface, dependency, or behaviour beyond restart timing is introduced.
Summary
A stdio server's identity today is the bytes of its executable (ExecIdentity, pkgs/mcpx/internal/config/execid.go). That is wrong for a program that finds its own files relative to its real location — the caveat is written into execid.go itself and into pkgs/mcpx/TODO.md item 4:
- A node script beside its
node_modules, re-pointed at a byte-identical copy in another directory, keeps its child under the automatic check (internal/pool/execwatch.go) and on reload — the child keeps running with the old directory's dependencies. Only a manualmcpx restart <server>moves it. - The pinning test is
TestReloadFollowsAForRePointedCommandByWhatItRuns(internal/daemon/reload_identity_test.go:162), whose comment carries the same caveat. - Above
limits.execHashMaxthe identity is the inode instead, so an oversized copy is restarted — the current rule is not even self-consistent: whether a moved copy restarts depends on its size.
Roadmap: planning/roadmaps/mcpx.md → Next → "Decide what a stdio server's identity is" (status proposed, linked only to the TODO — no prior issue covers it; searched 2026-10-27 for ExecIdentity / stdio identity issues, none found).
Proposal — directory-augmented identity for scripts, everything else unchanged:
ExecStamp.Identity()(internal/config/execid.go:114) is the single place identity is computed (used byResolved.ExecState, pool constructioninternal/pool/pool.go:199, and the periodic watcherinternal/pool/execwatch.go:84). When it hashes a small file, it also notes whether the content begins with#!. For a script, the resolved directory —filepath.Dirof the stamp's already-symlink-resolvedPath— is folded into the hash under a distinct framing. The identity string format (sha256:<16 hex>) does not change.- Directory, not full path: a rename or re-point within the same directory keeps the child (the location a script resolves siblings against has not moved); a re-point across directories changes the identity, so reload and the exec watch restart the pool through the existing
replaceChildrenpath with the existingserver.restart/ "executable changed" lifecycle event. No watcher, pool, or daemon wiring changes are needed — the stamp already moves on a re-point, and identity comparison already decides. - Non-script hashed files keep pure content identity (the deliberate anti-churn rule for binaries:
npx/uvx/nix-profile re-points at identical bytes keep their child). Large files keep stat identity. Missing commands and non-stdio servers are untouched.
Grounding: ExecStamp.Path is already the EvalSymlinks-resolved real path (execid.go:94-110), so the directory folded in is the location the kernel/interpreter actually runs the script from. Both existing identical-copy tests (TestExecIdentityOfASmallFileIsItsContentNotItsPath, TestReloadFollowsAForRePointedCommandByWhatItRuns, TestExecWatchFollowsARepointedLinkByWhatItRuns) place their copies in one directory, so they keep passing under the directory rule — the rule change is visible exactly and only where the current tests are silent: copies in different directories. One exact-hash assertion does change: TestExecIdentityFollowsTheExecutableNotTheCommandName pins a #!/bin/sh script's identity to its bare content hash; Phase 1 updates that expectation to the framed script hash.
Alternatives (argued in the design doc): directory for all hashed files (pays binary restart churn to fix a script problem); always restart on any re-point (discards the content-identity anti-churn design the exec watch exists around); full path for scripts (a rename within a directory is not a location change for sibling resolution); per-script location-sensitivity detection (not statically knowable); status quo + manual restart (the TODO already calls the current choice wrong).
Risks: a one-time identity change for every script server when this lands — recorded handoff identities mismatch, pools are not adopted, each script pool restarts once at upgrade/first check (the safe direction; it is exactly the existing binary-changed path). Location-independent scripts moved across directories (e.g. a wrapper that only execs an absolute path) now restart once needlessly — one drained child restart, correctness favoured over that churn. Residual gaps stay gaps, named as open questions in the design doc: native binaries that load sibling data ($ORIGIN-style), and interpreter-as-command servers (node script.js, where the script is an argument, not the command — its file is not part of ExecIdentity at all).
Phased breakdown
- Phase 1 — identity core in
internal/config. Script detection + directory folding inExecStamp.Identity(), theexecid.goheader caveat rewritten as the new rule, unit tests: cross-directory identical script differs, same-directory identical script keeps, cross-directory identical non-script keeps, large-file stat identity unchanged. Filed as a sub-issue of this issue. - Phase 2 — daemon-level proof + closure. Reload and exec-watch tests for a cross-directory re-point (pool replaced, child pid changes, one
server.restartevent) and a same-directory re-point (kept), on both the reload path and the live watch path;pkgs/mcpx/TODO.mditem 4 and the roadmap row closed out. Filed as a sub-issue of this issue, gated on Phase 1.
Open questions (NOT filed; stay as questions)
- Native binaries with sibling-data loading (
$ORIGINrpaths,../sharerelative tobin/): extend the directory rule to all hashed files if fleet evidence shows a real stale-location case? - Interpreter-as-command (
node/python+ script path as an argument): should the first script-like argument join the watched identity? A different mechanism (arguments are config, inPoolID, but the file behind the path is unwatched) — separate design question. - Should the restart lifecycle event distinguish "bytes changed" from "location changed" for operator visibility, or is
pathon the existing event enough?
When both phases land, this design's acceptance includes removing TODO item 4 and updating the planning/roadmaps/mcpx.md row — and noting the resolution on this issue.
Acceptance criteria
- Phase 1 (mcpx: Phase 1 of #1531 — exec identity core: a script's identity folds in its resolved directory (internal/config) #1532, identity core in
internal/config) and Phase 2 (mcpx: Phase 2 of #1531 — daemon proof: cross-directory re-point restarts on reload and the exec watch; TODO/roadmap closure #1533, daemon proof + closure) are both filed as sub-issues of this design — done at filing time — and both land. - The recorded identity decision is implemented as written:
#!script identity = content + resolved directory; non-script hashed files stay content-only; files abovelimits.execHashMaxstay stat/inode. -
pkgs/mcpx/TODO.mditem 4 is removed and theplanning/roadmaps/mcpx.mdrow "Decide what a stdio server's identity is" is updated to link this issue. - A completion note is posted on this issue summarising the resolution; the three open questions (native sibling-data binaries, interpreter-as-command, restart-event reason) remain recorded here as questions, not silently dropped.
Activity
developing-today commentedon Oct 28, 2026
CODE-587
dezren39 commentedon Oct 28, 2026
Intake review
Fleet-monitor intake pass (2026-10-28): verified still live — this is the recorded design decision for mcpx stdio exec identity, filed today by the design architect with both implementation phases already filed, so it does not sit silently: Phase 1 #1532 and Phase 2 #1533 carry the work and both are queue/queued.
pkgs/mcpx/TODO.mditem 4 and theplanning/roadmaps/mcpx.mdrow.## Acceptance criteriasection has been appended (original body preserved verbatim above it), restating the body's own completion conditions including the TODO/roadmap closure this issue promises when both phases land.dezren39 commentedon Oct 28, 2026
Phase 2 triage recorded (queue task 1575, Lane A, 2026-10-27): Phase 1 (#1532, PR #1571 merged 2026-10-28T03:58:50Z) satisfies the Phase 2 gate. Phase 2 (#1533) scope confirmed as built — daemon reload + exec-watch proof for cross-directory re-point restarts, TODO item 4 and roadmap identity-row closure. Full triage, including the keep-vs-restart nuance in the existing re-point tests the implementation must reconcile: #1533 (triage comment). Implementation enqueued as a follow-up queue task.
dezren39 commentedon Oct 28, 2026
Completion note — #1531 design closed out by Phase 2 (#1533, PR #1591).
As built, a stdio server's executable identity (
ExecIdentity,internal/config/execid.go, Phase 1 #1532 / PR #1571) is:#!) folds in the resolved directory of the real file, because a script finds its own files relative to its real location;limits.execHashMax) is identified by device/inode/size/mtime/mode;Phase 2 (PR #1591) proves the script rule end to end on both paths that consume identity:
TestReloadRestartsAScriptRePointedAtAnIdenticalCopyInAnotherDirectory— a link re-pointed at a byte-identical script in another directory replaces the pool (new child, old child gone); the existing same-directory re-point test still keeps its child, and its stale caveat comment now says so.TestExecWatchRestartsAScriptRePointedAtAnIdenticalCopyInAnotherDirectory— the same re-point on a live pool restarts within the watch ticks with exactly oneserver.restartevent (reason "executable changed"); the neighbour pool is untouched.Closure records in the same PR:
pkgs/mcpx/TODO.mditem 4 removed, and theplanning/roadmaps/mcpx.mdidentity row marked decided with citations to #1531/#1532. Fullinternal/daemonandinternal/poolsuites pass locally; CI on PR #1591 is the merge gate.test(mcpx): prove cross-directory script re-point restarts on reload …
dezren39 commentedon Oct 28, 2026
Program tending — completion check (2026-10-28)
Both sub-issues of this design are now closed:
internal/config) — closed 2026-10-28T03:58ZResolution, per the recorded decision: a hashed
#!script's exec identity is its content plus the resolved directory of the real file; non-script hashed files stay content-only; files abovelimits.execHashMaxstay stat/inode. #1533 proved it end to end on both the reload and exec-watch paths (cross-directory re-point restarts, same-directory re-point keeps the child) and closed outpkgs/mcpx/TODO.mditem 4. Theplanning/roadmaps/mcpx.mdrow now readsdecidedand links #1531/#1532. The three open questions in the body (native sibling-data binaries, interpreter-as-command, restart-event reason) remain recorded here as questions, as the acceptance criteria require.Proposing closure of this epic — every acceptance item that can be checked from the thread is met. One judgment call left for whoever closes it: the mcpx roadmap row still sits in Next as a
decidedrow recording this outcome; roadmaps are forward-looking only, so dropping that row at closure would be consistent — left to the closer rather than done silently here.— program tender (issue #1352 run, 2026-10-28)
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done — completion note for this design (queue task 2605 triage, Lane A)
pkgs/mcpx/internal/config/execid.gocomputes a script's identity as content + resolved directory: the header (lines 33–45) states the new rule, andIdentity()foldsfilepath.Dir(s.Path)into the hash under the NUL-led framing (line ~153) when the hashed file begins with#!. Non-script hashed files stay content-only; files abovelimits.execHashMaxstay stat/inode — exactly the recorded decision.pkgs/mcpx/TODO.mditem 4 is removed (the file's remaining items are unrelated), and theplanning/roadmaps/mcpx.mdrow "Decide what a stdio server's identity is" is markeddecided, linking design(mcpx): what a stdio server's identity is — a script's identity includes its directory #1531 and mcpx: Phase 1 of #1531 — exec identity core: a script's identity folds in its resolved directory (internal/config) #1532.$ORIGINrpaths,../sharerelative tobin/): extend the directory rule to all hashed files only if fleet evidence shows a real stale-location case; (2) interpreter-as-command servers (node script.js): whether the first script-like argument should join the watched identity is a separate design question; (3) whether the restart lifecycle event should distinguish "bytes changed" from "location changed", or the existing event'spathis enough.