State file race condition when audit.sh runs concurrently #5

Closed
opened 2026-05-14 21:32:25 +02:00 by Claude · 0 comments
Collaborator

Problem

The save_state function uses a non-atomic read-modify-write sequence to update per-repo state files. The final echo ... >> "$state_file" append is not protected against concurrent access, and the grep > tmp && mv tmp pattern only covers one of the two write steps.

Location

audit.sh, lines 179–186 (save_state function):

if [[ -f "$state_file" ]]; then
    grep -v "^${branch} " "$state_file" > "${state_file}.tmp" 2>/dev/null || true
    mv "${state_file}.tmp" "$state_file"
fi
echo "${branch} ${sha}" >> "$state_file"

Also branch_needs_audit (lines 189–213) reads state without a lock.

Risk

If two instances of audit.sh run simultaneously (e.g., triggered by a cron job that overlaps with a manual run, or via --repo filters for different repos), they can race on the same .state file. Both instances may read an identical saved SHA, both decide auditing is needed, both run redundant audits, and both write overlapping or duplicate entries to the state file. Over time, duplicate entries can accumulate, causing branch_needs_audit to always trigger (always seeing current SHA != saved SHA from the stale duplicate line).

Suggested fix direction

Use a lockfile per-repo (e.g., flock "$state_file.lock" -c "...") around the read-modify-write cycle in both save_state and branch_needs_audit. Alternatively, restructure to use a single atomic write per branch entry using a temp file and mv.

Severity

minor

Found by

Automated audit by Claude Code

## Problem The `save_state` function uses a non-atomic read-modify-write sequence to update per-repo state files. The final `echo ... >> "$state_file"` append is not protected against concurrent access, and the `grep > tmp && mv tmp` pattern only covers one of the two write steps. ## Location `audit.sh`, lines 179–186 (`save_state` function): ```bash if [[ -f "$state_file" ]]; then grep -v "^${branch} " "$state_file" > "${state_file}.tmp" 2>/dev/null || true mv "${state_file}.tmp" "$state_file" fi echo "${branch} ${sha}" >> "$state_file" ``` Also `branch_needs_audit` (lines 189–213) reads state without a lock. ## Risk If two instances of `audit.sh` run simultaneously (e.g., triggered by a cron job that overlaps with a manual run, or via `--repo` filters for different repos), they can race on the same `.state` file. Both instances may read an identical saved SHA, both decide auditing is needed, both run redundant audits, and both write overlapping or duplicate entries to the state file. Over time, duplicate entries can accumulate, causing `branch_needs_audit` to always trigger (always seeing current SHA != saved SHA from the stale duplicate line). ## Suggested fix direction Use a lockfile per-repo (e.g., `flock "$state_file.lock" -c "..."`) around the read-modify-write cycle in both `save_state` and `branch_needs_audit`. Alternatively, restructure to use a single atomic write per branch entry using a temp file and `mv`. ## Severity minor ## Found by Automated audit by Claude Code
bc1bb closed this issue 2026-05-15 01:32:29 +02:00
Sign in to join this conversation.
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#5
No description provided.