Skip to content

fix: strip repo-local GIT_* env vars from claude -p subprocess#7

Closed
ktinubu wants to merge 12 commits intodbreunig:mainfrom
ktinubu:fix/git-index-worktree-corruption
Closed

fix: strip repo-local GIT_* env vars from claude -p subprocess#7
ktinubu wants to merge 12 commits intodbreunig:mainfrom
ktinubu:fix/git-index-worktree-corruption

Conversation

@ktinubu
Copy link
Copy Markdown

@ktinubu ktinubu commented Mar 31, 2026

Summary

  • Root cause: During git commit in a worktree, git sets GIT_INDEX_FILE and GIT_DIR. claude -p inherits these and its plugin init overwrites the worktree's index with plugin cache entries (~130 entries from huggingface-skills). Result: error: Error building trees and a destroyed index. 100% reproduction rate.
  • Fix: Strip all repo-local GIT_* env vars from the subprocess env in _call_claude(), keeping only safe transport/config vars (SSH, SSL, config). Pattern from pre-commit framework's no_git_env().
  • Also: Pre-commit hook now checks PLUMB_SKIP=1 as an escape hatch.

Key evidence

Scenario Index After Corrupted?
claude -p standalone in worktree 355 No
claude -p in pre-commit hook during git commit 141 Yes
claude -p in hook with cwd=/tmp 104 Yes
claude -p in hook with GIT_INDEX_FILE/GIT_DIR stripped 356 No
plumb hook in worktree (with fix) baseline+1 No

Full findings: ktinubu#1 (comment)

Test plan

  • 3 e2e tests in test_worktree_index_corruption.py (real git, real claude -p, no mocks): upstream bug canary, shell-level fix proof, plumb hook fix proof
  • Unit test verifies repo-local vars stripped and transport vars kept
  • PLUMB_SKIP test verifies hook script content
  • Full suite: 418 passed, 5 failed (preexisting, tracked in Fix sync skipping directory spec_paths #2)
pytest tests/test_worktree_index_corruption.py -v --timeout=300 -m slow
pytest tests/test_claude_code_lm.py tests/test_cli.py -v
pytest tests/ --ignore=tests/test_generated.py --timeout=30

Fixes #1

🤖 Generated with Claude Code

ktinubu and others added 12 commits March 30, 2026 15:58
_collect_source_summaries was walking every .py file via rglob without
consulting .plumbignore. In projects with large .venv directories this
caused tens of thousands of irrelevant files to be AST-parsed and sent
to the LLM, making plumb coverage extremely slow.

Reuse the existing parse_plumbignore/is_ignored helpers so patterns like
.venv/ are honoured during coverage analysis.
is_ignored now fnmatches directory patterns (e.g. .venv*/) against
the top-level path component, so .venv3.10/, .virtualenv3.10/ etc.
are matched by a single .venv*/ rule in .plumbignore.
When ANTHROPIC_API_KEY is not set, plumb now detects the `claude` CLI
and routes LLM calls through `claude -p` (non-interactive mode). This
lets users with a Claude Code subscription run plumb without a separate
API key.

- Add ClaudeCodeLM (dspy.BaseLM subclass) that shells out to `claude -p`
- Update get_lm() / validate_api_access() / get_program_lm() to fall
  back to CLI when no API key is set
- Update CodeModifier to use _call_claude() when no API key
- API key always takes precedence when set (zero regression)
The run_pytest_coverage() subprocess call now passes -m "not slow"
so that `plumb coverage` skips slow-marked tests, matching the
project convention of running quick tests by default.
- Add test_claude_code_lm_raw_call: smoke test for basic prompt/response
- Add test_claude_code_lm_parse_spec_single_file: end-to-end spec parsing
  through DSPy RequirementParser with a tiny markdown spec
- Register slow marker and set addopts to skip slow tests by default
- Add stderr logging to ClaudeCodeLM.forward() for debugging
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… and requirement parser

Previously only question_synthesizer and decision_deduplicator respected
program_models config. Now code_coverage_mapper, test_mapper, and
requirement_parser also check for per-program LM overrides via
dspy.context(). Also adds model name to ClaudeCodeLM log output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nfig

Adds "budget" field to program_models entries so users can control
chunking granularity. Lower budgets create more parallel chunks,
which can improve latency for CLI-based LLM calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ook guard

_call_claude() now runs subprocess.run with cwd=tempfile.gettempdir() to
prevent Claude Code plugin init from corrupting git worktree indexes.
Pre-commit hook script now checks PLUMB_SKIP=1 for an escape hatch.

Fixes #1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two integration tests (real git, real claude -p, real worktree, no mocks):

- Test A: claude -p called directly from pre-commit hook
- Test B: plumb hook called from pre-commit hook (full code path)

Both assert the desired behavior: index stays intact and commit succeeds.
Currently FAIL because _call_claude() does not strip GIT_INDEX_FILE.

Refs #1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: during git commit in a worktree, git sets GIT_INDEX_FILE
and GIT_DIR. claude -p inherits these and its plugin init overwrites
the worktree's index with plugin cache entries (~130 entries).

Strips all repo-local GIT_* env vars from the subprocess env in
_call_claude(), keeping only safe transport/config vars (GIT_SSH,
GIT_CONFIG_*, etc.). Pattern from pre-commit framework's no_git_env().
Removes ineffective cwd=tempfile.gettempdir().

Three e2e tests (real git, real claude -p, real worktree, no mocks):
- Test A: proves raw claude -p corrupts the index (upstream bug)
- Test B: proves shell-level env stripping prevents corruption
- Test C: proves plumb hook protects the index via _call_claude() fix

Fixes #1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ktinubu ktinubu closed this Apr 1, 2026
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.

1 participant