Lock state-file access to fix concurrent audit.sh races #13

Merged
bc1bb merged 1 commit from fix/state-file-race into master 2026-05-15 01:32:29 +02:00
Collaborator

Summary

Fixes #5save_state, branch_needs_audit, and get_new_branches all touched the same per-repo state files (.state, .branches) with no coordination. Two overlapping audit.sh runs (cron + manual, or two --repo invocations) could:

  • Both read the same saved SHA and both decide an audit is needed → redundant work
  • Both append entries to .state → duplicate lines that later make branch_needs_audit always trigger
  • Observe a half-mv'd .state file mid-update

Fix

Wrap the critical sections in subshells holding flock on a per-repo ${repo_name}.lock file:

  • Exclusive lock (flock 9) in save_state — the only mutator
  • Shared lock (flock -s 9) in branch_needs_audit and get_new_branches — readers can run concurrently with each other but not with a writer

Both readers also moved their file-existence check inside the lock, so they can't observe a torn mv mid-flight from a concurrent save_state.

Notes

  • Lock files live in $STATE_DIR next to .state / .branches, named ${repo_name}.lock. They're created on first use and persist (zero bytes, harmless).
  • Init mode (Phase 1.5) was left untouched — it's a one-shot setup, and locking it would expand scope past the issue.

Test plan

  • Single run still works correctly (state files updated as before)
  • Two audit.sh --repo X runs against the same repo serialize cleanly — no duplicate entries in .state
  • First run with no existing state file still triggers an audit (empty saved_sha path)

Found by

Automated audit by Claude Code (issue #5)

## Summary Fixes #5 — `save_state`, `branch_needs_audit`, and `get_new_branches` all touched the same per-repo state files (`.state`, `.branches`) with no coordination. Two overlapping `audit.sh` runs (cron + manual, or two `--repo` invocations) could: - Both read the same saved SHA and both decide an audit is needed → redundant work - Both append entries to `.state` → duplicate lines that later make `branch_needs_audit` always trigger - Observe a half-mv'd `.state` file mid-update ## Fix Wrap the critical sections in subshells holding `flock` on a per-repo `${repo_name}.lock` file: - **Exclusive lock** (`flock 9`) in `save_state` — the only mutator - **Shared lock** (`flock -s 9`) in `branch_needs_audit` and `get_new_branches` — readers can run concurrently with each other but not with a writer Both readers also moved their file-existence check inside the lock, so they can't observe a torn `mv` mid-flight from a concurrent `save_state`. ## Notes - Lock files live in `$STATE_DIR` next to `.state` / `.branches`, named `${repo_name}.lock`. They're created on first use and persist (zero bytes, harmless). - Init mode (Phase 1.5) was left untouched — it's a one-shot setup, and locking it would expand scope past the issue. ## Test plan - [ ] Single run still works correctly (state files updated as before) - [ ] Two `audit.sh --repo X` runs against the same repo serialize cleanly — no duplicate entries in `.state` - [ ] First run with no existing state file still triggers an audit (empty `saved_sha` path) ## Found by Automated audit by Claude Code (issue #5)
save_state, branch_needs_audit, and get_new_branches all touch the same
per-repo state files without coordination. If two audit.sh runs overlap
(e.g. cron + manual, or two --repo invocations), they can read identical
saved SHAs, both decide the audit is needed, and both append entries —
producing duplicate lines that later make branch_needs_audit always
trigger.

Wrap the critical sections in subshells holding flock on a per-repo
.lock file: exclusive in save_state (mutates), shared in
branch_needs_audit and get_new_branches (read-only). Both reads now
also do the file-existence check inside the lock so they cannot observe
a torn mv from a concurrent save_state.

Fixes #5.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bc1bb merged commit f67cafe0fc into master 2026-05-15 01:32:29 +02:00
Sign in to join this conversation.
No reviewers
No labels
shellcheck
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.

Dependencies

No dependencies set.

Reference
bc1bb/claude-code-audit!13
No description provided.