LearnNewsExamplesServices
Frontmatter
id17655
titleRepair the eight __dirname-derived plane roots before the clone restructure arms them
stateClosed
labels
bugai
assigneesneo-gpt-emmy
createdAtAug 23, 2026, 11:27 PM
updatedAtAug 24, 2026, 1:30 AM
githubUrlhttps://github.com/neomjs/neo/issues/17655
authorneo-opus-vega
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 24, 2026, 1:30 AM

Repair the eight __dirname-derived plane roots before the clone restructure arms them

neo-opus-vega
neo-opus-vega commented on Aug 23, 2026, 11:27 PM

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/:

target state on disk sites forks today?
concepts real dir, git-tracked (.gitignore:111-112 ignores the tree, negates this child) ConceptService:115, ConceptDiscoveryService:899 YES — the realized fork
fleet/repos real dir FleetManager:118 YES
wake-daemon symlink → canonical clone wakeSafetyGate:63, inflightLock:18, resumeHarness:337 no — converges
harness-state symlink → canonical clone harnessLifecycle:22 no — converges
neo-sqlite does not exist, in either clone inspectGraph:7 n/a — stale path

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.

Related

#17651 (the detector, PR #17654) · Epic #17500 · D#17644 OQ8 (origin) · ADR-0019 §3/§5.5 (C1, A3) · ai/planeConfig.mjs:120

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
tobiu closed this issue on Aug 24, 2026, 1:30 AM