Summary
When running ./scripts/generate-skills.sh generate with multiple providers in parallel, the spawned claude subprocess for a provider occasionally writes its generated skills/<provider>-webhooks/ tree into the main repository checkout instead of the per-provider worktree at .worktrees/<provider>, despite the generator passing the worktree path as cwd to execa. The result is that the worktree's commit is empty (or the generator reports success on a tree that doesn't contain the files), and the new skill ends up untracked in the main checkout.
Observed during
A batched run of 12 providers with --parallel 3 --model claude-opus-4-7:
IS_SANDBOX=1 ./scripts/generate-skills.sh generate \
claude-managed-agents discord gemini huggingface hubspot intercom \
linear mailgun notion paypal slack twilio \
--config providers.yaml --parallel 3 --model claude-opus-4-7
2 of 12 providers exhibited the issue (~17%):
claude-managed-agents — files generated into the main checkout's skills/claude-managed-agents-webhooks/; the worktree at .worktrees/claude-managed-agents was empty. Recovered manually by git mv-ing the tree into the worktree, committing, and pushing.
huggingface — same pattern; same manual recovery.
The other 10 providers landed in their worktrees correctly. All three subprocesses were running in parallel, so it isn't a race against a single shared file lock — it appears to be a path-resolution issue inside the claude CLI itself when its CWD is a git worktree.
Why this matters
- Silent partial failures. The generator's
git diff --stat check (or whatever it uses to confirm new files exist) reports 0 files changed for the affected worktree, but the subprocess may still report success or have iterated on test results from the other checkout. The acceptance thresholds can pass against files that aren't on the branch being committed.
- State leaks into the main checkout. The unrelated files appear as untracked in whatever branch the operator has checked out — easy to mis-stage on a subsequent commit.
- Hard to reproduce. It's intermittent and only shows up when running multiple worktree-based subprocesses in parallel.
Suspected root cause
The claude CLI does some path resolution against its CWD on startup. When the CWD is a git worktree (.worktrees/<provider>), it appears to occasionally resolve back to the parent repository's working tree — possibly via git rev-parse --show-toplevel (which returns the main worktree's path, not the linked worktree's path, unless --git-common-dir/--show-prefix is used appropriately).
A quick mitigation worth testing: have scripts/skill-generator/lib/cli-adapters/claude.ts pass --add-dir <workingDir> and/or set an explicit project-root hint in the prompt so the CLI cannot fall back to the parent tree.
Reproduction
- Check out this repository on a system where you can run the generator (
IS_SANDBOX=1 if running as root — see AGENTS.md).
- Run the generator with at least 3 providers in parallel against fresh worktrees.
- After completion, inspect
git status in the main checkout — if any skills/<provider>-webhooks/ directories appear as untracked, the bug fired. Check the corresponding worktree at .worktrees/<provider> — it should be empty.
Not deterministic, but at ~17% per run with 3 parallel subprocesses the failure rate is high enough to investigate.
Workaround for now
When operating the generator in batch, after each provider's "Completed successfully!" event:
# Sanity check
ls .worktrees/<provider>/skills/<provider>-webhooks/
git -C . status --short # should be empty in the main checkout
If files leaked to the main checkout, git mv them into the worktree, then commit + push from the worktree.
Suggested fix candidates
- Inspect what path the
claude CLI resolves when CWD is a worktree — log it from a wrapper and confirm whether git rev-parse --show-toplevel returns the worktree path or the main path in that context.
- In
claude.ts, pass --add-dir <workingDir> explicitly so the agent has a clear scoped directory regardless of what the CLI auto-detects.
- Add a post-generation invariant in the generator: assert that
git -C <workingDir> diff --stat HEAD reports new files in skills/<provider>-webhooks/. Fail the provider (instead of returning success) if the worktree is empty.
- Optionally, before committing each worktree, run
git -C <mainRepo> status --porcelain skills/<provider>-webhooks/ and refuse to proceed if a leak is detected — surfaces the bug instead of swallowing it.
Found while running the new-provider generation batch that opened PRs #41–#52.
Summary
When running
./scripts/generate-skills.sh generatewith multiple providers in parallel, the spawnedclaudesubprocess for a provider occasionally writes its generatedskills/<provider>-webhooks/tree into the main repository checkout instead of the per-provider worktree at.worktrees/<provider>, despite the generator passing the worktree path ascwdtoexeca. The result is that the worktree's commit is empty (or the generator reports success on a tree that doesn't contain the files), and the new skill ends up untracked in the main checkout.Observed during
A batched run of 12 providers with
--parallel 3 --model claude-opus-4-7:2 of 12 providers exhibited the issue (~17%):
claude-managed-agents— files generated into the main checkout'sskills/claude-managed-agents-webhooks/; the worktree at.worktrees/claude-managed-agentswas empty. Recovered manually bygit mv-ing the tree into the worktree, committing, and pushing.huggingface— same pattern; same manual recovery.The other 10 providers landed in their worktrees correctly. All three subprocesses were running in parallel, so it isn't a race against a single shared file lock — it appears to be a path-resolution issue inside the
claudeCLI itself when its CWD is a git worktree.Why this matters
git diff --statcheck (or whatever it uses to confirm new files exist) reports0files changed for the affected worktree, but the subprocess may still report success or have iterated on test results from the other checkout. The acceptance thresholds can pass against files that aren't on the branch being committed.Suspected root cause
The
claudeCLI does some path resolution against its CWD on startup. When the CWD is a git worktree (.worktrees/<provider>), it appears to occasionally resolve back to the parent repository's working tree — possibly viagit rev-parse --show-toplevel(which returns the main worktree's path, not the linked worktree's path, unless--git-common-dir/--show-prefixis used appropriately).A quick mitigation worth testing: have
scripts/skill-generator/lib/cli-adapters/claude.tspass--add-dir <workingDir>and/or set an explicit project-root hint in the prompt so the CLI cannot fall back to the parent tree.Reproduction
IS_SANDBOX=1if running as root — see AGENTS.md).git statusin the main checkout — if anyskills/<provider>-webhooks/directories appear as untracked, the bug fired. Check the corresponding worktree at.worktrees/<provider>— it should be empty.Not deterministic, but at ~17% per run with 3 parallel subprocesses the failure rate is high enough to investigate.
Workaround for now
When operating the generator in batch, after each provider's "Completed successfully!" event:
If files leaked to the main checkout,
git mvthem into the worktree, then commit + push from the worktree.Suggested fix candidates
claudeCLI resolves when CWD is a worktree — log it from a wrapper and confirm whethergit rev-parse --show-toplevelreturns the worktree path or the main path in that context.claude.ts, pass--add-dir <workingDir>explicitly so the agent has a clear scoped directory regardless of what the CLI auto-detects.git -C <workingDir> diff --stat HEADreports new files inskills/<provider>-webhooks/. Fail the provider (instead of returning success) if the worktree is empty.git -C <mainRepo> status --porcelain skills/<provider>-webhooks/and refuse to proceed if a leak is detected — surfaces the bug instead of swallowing it.Found while running the new-provider generation batch that opened PRs #41–#52.