Skip to content

hooks: auto-ignore .devkit/ in host repos on first run - #57

Merged
5uck1ess merged 2 commits into
mainfrom
fix/audit-log-auto-gitignore
Apr 10, 2026
Merged

hooks: auto-ignore .devkit/ in host repos on first run#57
5uck1ess merged 2 commits into
mainfrom
fix/audit-log-auto-gitignore

Conversation

@5uck1ess

Copy link
Copy Markdown
Owner

Summary

  • hooks/audit-trail.sh writes .devkit/audit.log on every Bash tool call, but the plugin can't reach into downstream repos' .gitignore. Users whose project .gitignore didn't already cover .devkit/ end up tracking the log on their next git add ., then hit conflicts on every git stash pop / pull / merge because the hook rewrites it on every subsequent command.
  • This PR makes the hook self-install .devkit/ into the host repo's root .gitignore on first run (detected via .devkit/ directory not existing yet), using git rev-parse --show-toplevel to find the repo root.
  • Idempotent (grep -qE '^\.devkit($|/)' skips if any matching entry already exists), worktree-safe, silently no-ops outside a git repo, and guards behind [[ ! -d \"\$LOG_DIR\" ]] so the check only forks git once per repo — zero per-command overhead thereafter.

What this does NOT fix

Repos that already committed .devkit/audit.log before this change. .gitignore rules don't apply to already-tracked files — affected users need a one-time:
```
git rm --cached .devkit/audit.log && git commit -m "untrack audit log"
```

Test plan

  • Fresh repo with no .gitignore: trigger any Bash tool call, confirm .gitignore is created containing .devkit/ and .devkit/audit.log is untracked in git status.
  • Existing repo with .gitignore that doesn't end in a newline: confirm the appended entry lands on its own line.
  • Existing repo with .gitignore already containing .devkit/: confirm no duplicate entry is appended.
  • Existing repo with .gitignore containing .devkit (no slash) or .devkit/audit.log: confirm regex matches and nothing is appended.
  • Inside a subdirectory of a repo: confirm the entry lands in the repo-root .gitignore, not the subdirectory.
  • Inside a git worktree: confirm git rev-parse --show-toplevel resolves correctly and the main repo's .gitignore is updated.
  • Outside any git repo: confirm the hook still logs to .devkit/audit.log and exits 0 without error.

audit-trail.sh writes .devkit/audit.log on every Bash tool call. If the
host repo's .gitignore doesn't already cover .devkit/, that file gets
tracked on the first `git add .` and then conflicts on every subsequent
stash pop / pull / merge because the hook keeps rewriting it.

devkit's own .gitignore already has .devkit/, but the plugin can't reach
into downstream repos — so every new install is one `git add .` away
from this trap.

Fix: on first run in a repo (detected via `.devkit/` not yet existing),
`git rev-parse --show-toplevel` to find the repo root and append
`.devkit/` to the root .gitignore if no matching entry exists. Idempotent
(grep -qE '^\.devkit($|/)' skips if already present), worktree-safe
(rev-parse handles worktrees), fails silently outside a git repo, and
runs only once per repo so there's no per-command fork overhead after
the first call.

Does not rescue repos that already tracked audit.log before this change —
those need a one-time `git rm --cached .devkit/audit.log`.
Review on PR #57 flagged a TOCTOU window in the first-run guard:
parallel Claude Code Bash tool calls can both pass the `[[ ! -d .devkit ]]`
check and both append `.devkit/` to the host repo's .gitignore, leaving
a duplicate line. Harmless to git, but visible noise.

Fix: switch the guard to an atomic noclobber init marker.
  1. Fast-path check: `[[ -f .devkit/.gitignore-installed ]]` (no fork,
     just a stat — runs on every hook call but zero overhead after init).
  2. Critical section: `( set -C; : > .gitignore-installed )` uses POSIX
     noclobber in a subshell, so only one process can successfully create
     the marker. Losers fail the redirection silently and skip the git
     work; the winner does the rev-parse + grep + append.

Also documents the intentional `--show-toplevel` behavior in submodules
(reviewer flagged as a potential bug, but it is correct: `.devkit/` is
created relative to cwd, so the submodule's own .gitignore is the right
file when running inside a submodule).

Passes shellcheck.
@5uck1ess
5uck1ess merged commit e3932d5 into main Apr 10, 2026
4 checks passed
@5uck1ess
5uck1ess deleted the fix/audit-log-auto-gitignore branch April 10, 2026 18:54
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