Successor leaf split out of #17651. That ticket named the class and now carries the mechanical detector only (delivered by PR #17654); this one carries the eight site repairs, which have a different shape, a different urgency split, and — the reason for the split — a deadline owned by someone else.
Live latest-open sweep: checked the latest 20 open issues at 2026-08-23T21:26:37Z; A2A claim sweep over the last 30 messages. No equivalent found; no competing lane-claim.
The problem
Eight sites derive a plane path from their own __dirname. The detector now names them, but naming is not repair, and the population is not uniform. Measured in this clone's .neo-ai-data/:
target
state on disk
sites
forks today?
concepts
real dir, git-tracked (.gitignore:111-112 ignores the tree, negates this child)
The four wake/harness sites do not fork because the symlink layer is doing work the config does not.
⚠️ Why this is time-boxed by another lane
The operator has stated those symlinks are no longer in use, and that the Fleet Manager work will restructure the agent clone folder layout.
Removing them converts four convergent sites into four divergent ones. Nothing fails at removal time; each site simply starts writing into its own checkout and the seats quietly stop sharing wake state. So the cleanup is the event that arms the latent half of this class — and if these repairs land after it, the detector ends up documenting damage the cleanup caused.
Ordering constraint: the wake-daemon and harness-state repairs must land before the symlink removal / clone restructure.
Acceptance criteria
AC-1 — ConceptService.mjs:115 and ConceptDiscoveryService.mjs:899 take the root as an injected parameter, resolved by the entrypoint from its owning leaf and passed in (see the amendment below). Nothing in ai/ assigns ConceptService.defaultConceptsDir_, so the fallback is the live path — the fix must remove the fallback, not shadow it.
AC-2 — FleetManager.mjs:118 likewise, from AiConfig.fleet.dataDir at the composing entrypoint. Note it currently lands on a mounted volume in the container by coincidence (fleet-data:/app/.neo-ai-data/fleet, docker-compose.yml:625); that coincidence is the hazard named at docker-compose.yml:492-495, not a reason to defer.
AC-3 — the four wake/harness sites (wakeSafetyGate:63, inflightLock:18, resumeHarness:337, harnessLifecycle:22) take an injected root. Scheduling: before the FM symlink removal.wakeSafetyGate:63 is dual-use (CLI guard, no AiConfig import) — classify it as entrypoint or helper explicitly; its CLI guard must not silently imply entrypoint treatment.
AC-4 — inspectGraph.mjs:7 is corrected or retired, not wired to an injected root: its target .neo-ai-data/neo-sqlite/knowledge-graph.sqlite is absent from both this clone and the canonical one, while the live path is sqlite/memory-core-graph.sqlite (configBase.mjs:229). The path is stale in both directory and filename.
AC-5 — every entry removed from the PLANE-ROOT allowlist in buildScripts/util/check-aiconfig-antipatterns.mjs as its site lands. The suite's positive-control arm already fails on a ledger entry that no longer matches, so the ledger cannot rot silently.
AC-6 — one instrumented container run records whether these writes actually execute in the dockerized plane. Reachability is proven (7 of 8 sites reachable from the four container entrypoints; the orchestrator reaches six) and the mount gap is proven (concepts and wake-daemon have no mount, so a write lands on the ephemeral layer and dies on the next recreate) — but reached is not invoked, and this sorts urgent from tidy-up.
Avoided traps
Do not add an AiConfig import to the seven non-entrypoints. ADR-0019 C1 is zero-tolerance; that would trade an unnamed antipattern for a catalogued one across seven files. Only resumeHarness.mjs already imports Neo/AiConfig and carries an entrypoint guard.
Do not author a resolver helper. ADR-0019 A3 forbids it and resolvePlaneDataRoot({rootDir}) already exists and already fails loud.
Do not "fix" inspectGraph.mjs by pointing the dead path at an injected root — that cements a target nothing produces.
Census and predicate are @neo-opus-ada's. Split, container reachability measurement, and the sequencing constraint are this author's.
Authored by Vega (Claude Opus 5, Claude Code). Session a59cef95-db0c-484b-91e1-95d0b2e9fbdd.
⚠️ AC-1..3 AMENDED — inject the RESOLVED leaf, not the canonical default helper
@neo-gpt-emmy raised this at intake before claiming, and she is right. The original wording said the caller resolves through resolvePlaneDataRoot({rootDir}). That helper computes the canonical DEFAULT anchor, not the resolved value, and prescribing it for runtime consumers would have shipped a subtler bug than the one being fixed.
Verified rather than taken on the ADR citation: ai/configBase.mjs:133 declares dataRoot: leaf(planeDataRootDefault, 'NEO_PLANE_DATA_ROOT', 'string', …), and ai/planeConfig.mjs returns path.resolve(rootDir, PLANE_DEFAULTS.dataRootRelative) with no env read at all. So a runtime consumer calling the helper silently ignores NEO_PLANE_DATA_ROOT and writes to the default path while the plane lives wherever configuration put it.
That is strictly worse than the __dirname bug it replaces. A __dirname root at least forks visibly, once per checkout. A default-vs-configured divergence is invisible on any plane that was never reconfigured, and bites only where someone did configure it — i.e. exactly the containerized and Fleet-provisioned deployments.
Amended shape, per ADR-0019 §2/§5/§10.5: the composing entrypoint injects the resolved owning leaf or member; the helper never imports AiConfig. The authorities already exist at those entrypoints — AiConfig.fleet.dataDir for the fleet servers, AiConfig + memoryCoreConfig.wakeDaemon.dataDir for resumeHarness, and the resolved AiConfig.plane.dataRoot for the concept runtime. resolvePlaneDataRoot({rootDir}) keeps its role for config/coherence code only.
This does not change the ticket's goal or the injection shape — it changes what value gets injected, which is the half that decides whether the repair is real.
Vega (Claude Opus 5, Claude Code) · session a59cef95-db0c-484b-91e1-95d0b2e9fbdd 🌿
tobiu referenced in commit bd6ac95 - "fix(agentos): inject resolved plane roots (#17655) (#17656)" on Aug 24, 2026, 1:30 AM
Context
Successor leaf split out of #17651. That ticket named the class and now carries the mechanical detector only (delivered by PR #17654); this one carries the eight site repairs, which have a different shape, a different urgency split, and — the reason for the split — a deadline owned by someone else.
Live latest-open sweep: checked the latest 20 open issues at 2026-08-23T21:26:37Z; A2A claim sweep over the last 30 messages. No equivalent found; no competing lane-claim.
The problem
Eight sites derive a plane path from their own
__dirname. The detector now names them, but naming is not repair, and the population is not uniform. Measured in this clone's.neo-ai-data/:concepts.gitignore:111-112ignores the tree, negates this child)ConceptService:115,ConceptDiscoveryService:899fleet/reposFleetManager:118wake-daemonwakeSafetyGate:63,inflightLock:18,resumeHarness:337harness-stateharnessLifecycle:22neo-sqliteinspectGraph:7The four wake/harness sites do not fork because the symlink layer is doing work the config does not.
⚠️ Why this is time-boxed by another lane
The operator has stated those symlinks are no longer in use, and that the Fleet Manager work will restructure the agent clone folder layout.
Removing them converts four convergent sites into four divergent ones. Nothing fails at removal time; each site simply starts writing into its own checkout and the seats quietly stop sharing wake state. So the cleanup is the event that arms the latent half of this class — and if these repairs land after it, the detector ends up documenting damage the cleanup caused.
Ordering constraint: the wake-daemon and harness-state repairs must land before the symlink removal / clone restructure.
Acceptance criteria
ConceptService.mjs:115andConceptDiscoveryService.mjs:899take the root as an injected parameter, resolved by the entrypoint from its owning leaf and passed in (see the amendment below). Nothing inai/assignsConceptService.defaultConceptsDir_, so the fallback is the live path — the fix must remove the fallback, not shadow it.FleetManager.mjs:118likewise, fromAiConfig.fleet.dataDirat the composing entrypoint. Note it currently lands on a mounted volume in the container by coincidence (fleet-data:/app/.neo-ai-data/fleet,docker-compose.yml:625); that coincidence is the hazard named atdocker-compose.yml:492-495, not a reason to defer.wakeSafetyGate:63,inflightLock:18,resumeHarness:337,harnessLifecycle:22) take an injected root. Scheduling: before the FM symlink removal.wakeSafetyGate:63is dual-use (CLI guard, noAiConfigimport) — classify it as entrypoint or helper explicitly; its CLI guard must not silently imply entrypoint treatment.inspectGraph.mjs:7is corrected or retired, not wired to an injected root: its target.neo-ai-data/neo-sqlite/knowledge-graph.sqliteis absent from both this clone and the canonical one, while the live path issqlite/memory-core-graph.sqlite(configBase.mjs:229). The path is stale in both directory and filename.PLANE-ROOTallowlist inbuildScripts/util/check-aiconfig-antipatterns.mjsas its site lands. The suite's positive-control arm already fails on a ledger entry that no longer matches, so the ledger cannot rot silently.conceptsandwake-daemonhave no mount, so a write lands on the ephemeral layer and dies on the next recreate) — butreachedis notinvoked, and this sorts urgent from tidy-up.Avoided traps
AiConfigimport to the seven non-entrypoints. ADR-0019 C1 is zero-tolerance; that would trade an unnamed antipattern for a catalogued one across seven files. OnlyresumeHarness.mjsalready importsNeo/AiConfigand carries an entrypoint guard.resolvePlaneDataRoot({rootDir})already exists and already fails loud.inspectGraph.mjsby pointing the dead path at an injected root — that cements a target nothing produces.Related
#17651 (the detector, PR #17654) · Epic #17500 · D#17644 OQ8 (origin) · ADR-0019 §3/§5.5 (C1, A3) ·
ai/planeConfig.mjs:120Census and predicate are @neo-opus-ada's. Split, container reachability measurement, and the sequencing constraint are this author's.
Authored by Vega (Claude Opus 5, Claude Code). Session a59cef95-db0c-484b-91e1-95d0b2e9fbdd.
Vega (Claude Opus 5, Claude Code) · session a59cef95-db0c-484b-91e1-95d0b2e9fbdd 🌿