Skip to content

Fall back to the main checkout's graph from a git worktree (#2008) - #3635

Open
ayushcodes10 wants to merge 3 commits into
Graphify-Labs:v8from
ayushcodes10:fix-2008-worktree-graph-fallback
Open

ayushcodes10 wants to merge 3 commits into
Graphify-Labs:v8from
ayushcodes10:fix-2008-worktree-graph-fallback

Conversation

@ayushcodes10

Copy link
Copy Markdown
Contributor

Summary

Fixes #2008. The graph (`graphify-out/`) lives in the checkout that built it, but a bare `graphify explain`/`query`/`path`/`affected`/`god-nodes` run from inside a git worktree failed with "graph file not found," since the worktree has no `graphify-out/` of its own. AI-agent workflows that run every task in its own worktree hit this constantly, and previously needed a manual `cd` wrapper carried in project rules that breaks when the cwd disappears mid-session.

Fix

`_default_graph_path()` (the shared default every read-only graph command falls back to when `--graph` isn't given) now:

  1. Checks the usual `graphify-out/graph.json` relative to the current directory first — unchanged behavior when it exists.
  2. Only when that's absent, resolves `git rev-parse --git-common-dir` (the shared `.git` a worktree and its main checkout both point at) and looks for the graph in its parent — the main checkout's root.

This is read-only and degrades silently to the ordinary (non-existent) default path on any failure: not a git repository, git missing from `PATH`, or no graph anywhere — so the existing "graph file not found" error still surfaces normally in those cases, just with one more location checked first. A worktree that has its own graph built directly in it (not the common case, but possible) still uses that one, not the fallback.

Test plan

  • New `tests/test_worktree_graph_fallback.py` builds real git repos and worktrees under `tmp_path` rather than mocking `subprocess`, since the point is git's own `--git-common-dir` resolution behaving as expected. Covers: the fallback firing correctly, a main checkout with no graph anywhere, a worktree with no graph anywhere, a plain non-git directory, and a worktree with its own graph correctly preferring that one over the fallback.
  • Manually verified with a real `git worktree add` against this repo before writing the automated tests.
  • Full suite: `python3 -m pytest -q` — 5626 passed, 68 skipped, no regressions.

🤖 Generated with Claude Code

ayushcodes10 and others added 3 commits September 17, 2026 20:10
A worktree shares history with the main checkout but has no
graphify out directory of its own, so a bare query, explain, path,
affected, or god nodes run failed with "graph file not found" even
though the graph exists one worktree away. AI agent workflows that
run every task in its own worktree hit this constantly, and
previously needed a manual cd wrapper carried in project rules.

The default graph path now checks the usual location relative to
the current directory first, and only when that is absent, resolves
git's shared common directory and looks for the graph in its parent,
the main checkout's root. This is read only and degrades silently to
the ordinary path on any failure: not a git repository, git missing
from PATH, or no graph anywhere. A worktree that has its own graph
built directly in it still uses that one, not the fallback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Builds real git repos and worktrees under tmp_path rather than
mocking subprocess, since the point is git's own common directory
resolution behaving as expected. Covers the worktree fallback firing,
a main checkout with no graph anywhere returning the plain default,
a worktree with no graph anywhere doing the same, a directory that is
not a git repository at all, and a worktree that has its own graph
preferring that one over the fallback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) tested, no difference found (not proven).


Graphify review — findings

Resolves the default graph path to the main checkout's graphify-out/graph.json when a read-only command runs inside a git worktree that has no graphify-out/ of its own, so query/explain/path/etc. no longer fail with "graph file not found". _worktree_graph_fallback shells out to git rev-parse --git-common-dir and looks in its parent, firing only when the local candidate is absent and returning None (leaving the plain error to surface) on any git failure or when no graph exists anywhere.

Worth a look

  • git-common-dir parent is not the main checkout root for a worktreegraphify/cli.py:111 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Worktree fallback assumes the common git dir is inside the main checkoutgraphify/cli.py:111 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 495 functions depend on the 276 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 125 callees
  • new: _stale_graph_sources() — 7 callers, 6 callees
  • new: _run_hook_guard() — 4 callers, 8 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 495 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 433 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

287 of 287 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — impact, full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — impact, full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — impact, full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — full-run-safety
  • tests/test_cross_language_call_resolution.py — full-run-safety
  • tests/test_cross_repo_external_call_guards.py — full-run-safety
  • tests/test_cross_repo_member_calls.py — full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — full-run-safety
  • … and 237 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

No difference found (not proven): No behavior difference found in \_default\_graph\_path (not a proof).

The verifier ran both versions of \_default\_graph\_path on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

· 4 more finding(s) on lines outside this diff (see the check run).

@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Checked both flagged findings against the code. For the ordinary case (a linked worktree off a normal checkout), --git-common-dir's parent is by definition the main checkout root — that's the documented purpose of the flag, and it's what the fallback exists for.

The one real edge case is a checkout set up with a separate git dir (git clone --separate-git-dir=..., or a bare repo) where the common dir's parent is not the working tree root. In that setup the candidate path almost certainly won't exist, so candidate.is_file() is False and the function returns None — the existing plain "graph file not found" error still surfaces, exactly as the docstring already documents ("silently returns None on any failure ... so a plain error still surfaces from the normal path"). No crash, no wrong location read. Treating this as an accepted, already-documented scope limit rather than a bug — let me know if there's a concrete repro where it does something worse than falling through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto-resolve graph location from git worktrees (git-common-dir)

1 participant