wiki-knowledge

The session root resolves per host, and never falls back to cwd

Context. A session’s state belongs to the project the session is running in: Claude Code’s SessionStart hook writes .claude/wiki-knowledge/sessions/<session_id>.json, OpenCode’s session-tracker plugin writes the same shape under .opencode/. That project is never the vault — in query-from-anywhere mode the vault is somewhere else entirely (ADR-0004) — so “which project is this session’s?” is its own question with its own answer, and until now it had two: findSessionsDir (enchiridion-ts/src/sessionstate.ts) consulted $CLAUDE_PROJECT_DIR and stopped its .claude/ walk at $HOME, while openCodeSessionsDir (enchiridion-ts/src/transcriptcapture.ts) had no env override and no $HOME stop, ending in a cwd fallback. Same question, two answers, and the divergence was in the terminal rule — the part that has nothing to do with host layout (#493).

That divergence is not cosmetic. A cwd fallback is the exact mechanism of #485: a hook that resolved a root from wherever it happened to be standing scattered .claude/wiki-knowledge/sessions/ trees through content folders and split one session’s state across two directories. The rule that survived that fix — resolve the project, or write nothing — is the one worth having, and one of the two walks was still not applying it.

Decision — one order for the session root, with the host supplying only its layout. The session root resolves, highest priority first:

  1. The host’s own env override, when the host exports one: $CLAUDE_PROJECT_DIR for Claude Code, bound at session start so it does not move when the session runs cd or enters a worktree. OpenCode exports no equivalent, so this rule is skipped for it.
  2. The nearest ancestor of cwd — cwd included — holding that host’s marker directory: .claude/ or .opencode/. Writer and reader agree on a root even when cwd is a subdirectory.
  3. Nothing. The walk stops at $HOME — ~/.claude and ~/.opencode are a host’s global configuration, not markers for a project — and returning “no project” is the answer, never a cwd fallback.

Only two things differ per host: the marker directory and the env override. The walk, the $HOME stop and the refusal to guess are one shared rule (findProjectRoot), so a second host cannot grow a second answer to the same question.

Decision — this is not ADR-0004’s order, and the difference is the terminal rule. The vault root resolves $WIKI_ROOT → nearest ancestor with a vault marker (wiki/ or .wiki-root) → cwd itself. Same first two levels, opposite last one: a cwd fallback where this has a $HOME stop. So they are not one order, and the difference is deliberate rather than an oversight to be unified later.

The vault root’s fallback is load-bearing because of what its callers do: the commands that resolve it exist to operate on a vault, query-from-anywhere requires an answer for “which vault” even when cwd is not one, and enchiridion init turns that answer into a vault. The session root’s callers are hooks and skills, installed anywhere and running unattended; they ask not “which vault” but whose state is this, and there is exactly one project a session belongs to — one the host either states in the environment or marks on disk. When neither holds, there is no project to belong to, and inventing one is #485. A $HOME stop fits that shape and a fallback does not; a fallback fits the vault’s shape and a $HOME stop would break init.

Mechanism

Consequences

What would reopen this