detect_root: a parent's .code-index.toml captures nested git worktrees/clones — agent worktree sessions silently answer from the wrong tree #553

Closed
opened 2026-11-09 12:05:30 +01:00 by buildagent · 2 comments
Member

Summary

detect_root lets a .code-index.toml in a PARENT directory capture a nested git repository or worktree. Every agent session started inside a git worktree under the main checkout (.claude/worktrees/<lane>/) silently indexes and answers from the parent tree instead of its own. HEAD is usually identical, so the answers look right, but the agent's own edits are invisible to every tool.

Field evidence (2026-11-09, dogfood across 5 sessions)

  • Lane A (Claude) in .claude/worktrees/lane-a-binds: read_code on its own files → path_not_indexed (hidden .claude dir); project_overview showed the parent's uncommitted_changes: true while the lane was clean.
  • Codex workers codex-512, codex-546: code-index-mcp ran with cwd = the worktree (verified via /proc/<pid>/cwd) and served /home/master/code/rust/cosi-mcp. read_code on their own edited files → path_not_indexed; changed_symbols could not see their diffs.
  • codex-534: index_coverage(<own file>) → verdict: never, reason: hidden.

Cause (verified by reading the code)

crates/indexer/src/project.rs detect_root_within: tier 1 (.code-index.toml, innermost) walks ALL ancestors before tier 2 (.git) runs. The primary checkout had an untracked .code-index.toml, so a worktree whose only marker is its own .git file resolves to the parent. The same applies to a nested clone or submodule (.git directory) under any directory with a .code-index.toml.

Fix (in progress, branch codex-t1)

One upward walk. At each ancestor, check .code-index.toml and then .git (file or directory). The first (innermost) hit of either wins.

  • A .code-index.toml inside the repository still overrides the repository root, which keeps the monorepo sub-project semantics.
  • A .code-index.toml above the innermost .git boundary never captures that repository.
  • Tier 3 (outermost language marker, bounded at $HOME) is unchanged.

Acceptance

  • Tests: worktree (.git file) and nested clone (.git dir) under a dir with .code-index.toml → root is the nested repo; in-repo .code-index.toml still wins; outer toml + same-dir .git still wins.
  • Mutations: restore the two-pass walk (the first two go red); check .git before the toml in the single walk (in-repo override goes red).
  • After merge, remove the stray untracked .code-index.toml from the main checkout (operator-approved).

Related: #521 (worktree gaps 1–2; this was the actual cause of the lane-A field case recorded there).

## Summary `detect_root` lets a `.code-index.toml` in a PARENT directory capture a nested git repository or worktree. Every agent session started inside a git worktree under the main checkout (`.claude/worktrees/<lane>/`) silently indexes and answers from the **parent tree** instead of its own. HEAD is usually identical, so the answers look right, but the agent's own edits are invisible to every tool. ## Field evidence (2026-11-09, dogfood across 5 sessions) - Lane A (Claude) in `.claude/worktrees/lane-a-binds`: `read_code` on its own files → `path_not_indexed` (hidden `.claude` dir); `project_overview` showed the parent's `uncommitted_changes: true` while the lane was clean. - Codex workers `codex-512`, `codex-546`: `code-index-mcp` ran with cwd = the worktree (verified via `/proc/<pid>/cwd`) and served `/home/master/code/rust/cosi-mcp`. `read_code` on their own edited files → `path_not_indexed`; `changed_symbols` could not see their diffs. - `codex-534`: `index_coverage(<own file>)` → `verdict: never, reason: hidden`. ## Cause (verified by reading the code) `crates/indexer/src/project.rs` `detect_root_within`: tier 1 (`.code-index.toml`, innermost) walks ALL ancestors before tier 2 (`.git`) runs. The primary checkout had an untracked `.code-index.toml`, so a worktree whose only marker is its own `.git` file resolves to the parent. The same applies to a nested clone or submodule (`.git` directory) under any directory with a `.code-index.toml`. ## Fix (in progress, branch `codex-t1`) One upward walk. At each ancestor, check `.code-index.toml` and then `.git` (file or directory). The first (innermost) hit of either wins. - A `.code-index.toml` inside the repository still overrides the repository root, which keeps the monorepo sub-project semantics. - A `.code-index.toml` above the innermost `.git` boundary never captures that repository. - Tier 3 (outermost language marker, bounded at `$HOME`) is unchanged. ## Acceptance - Tests: worktree (`.git` file) and nested clone (`.git` dir) under a dir with `.code-index.toml` → root is the nested repo; in-repo `.code-index.toml` still wins; outer toml + same-dir `.git` still wins. - Mutations: restore the two-pass walk (the first two go red); check `.git` before the toml in the single walk (in-repo override goes red). - After merge, remove the stray untracked `.code-index.toml` from the main checkout (operator-approved). Related: #521 (worktree gaps 1–2; this was the actual cause of the lane-A field case recorded there).
Author
Member

Fixed on branch codex-t1 (835af751), merged into the integration branch integration-1109 (e5471419). It lands on master with the next integrated train.

  • One upward walk: at each ancestor .code-index.toml and then .git (file or dir); the innermost hit wins. Tier 3 is unchanged.
  • Tests: worktree_git_file_beats_outer_config, nested_git_dir_beats_outer_config, inner_config_overrides_git, config_and_git_at_root_return_root.
  • Mutation: the two-pass walk was restored. Both boundary tests go red, the two preservation tests stay green. The worker ran it, and the lead re-ran it independently (md5-verified restore, 28/28 green after).
  • Brief correction: the requested second mutation (checking .git before the toml in the single walk) cannot fail. At one ancestor both checks return the same path. The worker showed that and used other mutations instead.
  • Done: the stray untracked .code-index.toml in the main checkout was removed (backed up), as the operator approved.
Fixed on branch `codex-t1` (`835af751`), merged into the integration branch `integration-1109` (`e5471419`). It lands on master with the next integrated train. - One upward walk: at each ancestor `.code-index.toml` and then `.git` (file or dir); the innermost hit wins. Tier 3 is unchanged. - Tests: `worktree_git_file_beats_outer_config`, `nested_git_dir_beats_outer_config`, `inner_config_overrides_git`, `config_and_git_at_root_return_root`. - Mutation: the two-pass walk was restored. Both boundary tests go red, the two preservation tests stay green. The worker ran it, and the lead re-ran it independently (md5-verified restore, 28/28 green after). - Brief correction: the requested second mutation (checking `.git` before the toml in the single walk) cannot fail. At one ancestor both checks return the same path. The worker showed that and used other mutations instead. - Done: the stray untracked `.code-index.toml` in the main checkout was removed (backed up), as the operator approved.
Author
Member

Released since v0.41.48 (835af751). Checked again on master 3d0a584c (v0.41.50): the single upward walk and its tests (worktree_git_file_beats_outer_config, nested_git_dir_beats_outer_config in crates/indexer/src/project.rs) are in the tree and run in CI. Closing.

Released since v0.41.48 (`835af751`). Checked again on master `3d0a584c` (v0.41.50): the single upward walk and its tests (`worktree_git_file_beats_outer_config`, `nested_git_dir_beats_outer_config` in `crates/indexer/src/project.rs`) are in the tree and run in CI. Closing.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
h-dv/code-index#553
No description provided.