fix: resolve history-shadow.ts race condition (#479)#480
Merged
bradygaster merged 1 commit intobradygaster:devfrom Mar 22, 2026
Merged
fix: resolve history-shadow.ts race condition (#479)#480bradygaster merged 1 commit intobradygaster:devfrom
bradygaster merged 1 commit intobradygaster:devfrom
Conversation
appendToHistory() used readFile → regex insert → writeFile with no synchronization. Two concurrent agents appending to the same history.md caused last-write-wins data loss. Fix: wrap the read-modify-write cycle in an in-process async mutex (keyed by resolved file path) and use atomic writes (temp file + rename) to prevent partial reads. Zero new dependencies — uses only Node.js built-ins. - Adds withFileLock() async mutex that serializes concurrent callers per file while allowing parallelism across different files. - Adds atomicWriteFile() using temp file + fs.rename. - Adds 14 tests covering regression, concurrent race conditions (5 and 10 simultaneous appends), cross-section concurrency, lock/temp file cleanup, and full API contract verification. Fixes bradygaster#479 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
266efa4 to
ea7f4d9
Compare
bradygaster
approved these changes
Mar 22, 2026
Owner
bradygaster
left a comment
There was a problem hiding this comment.
EECOM review: Race condition fix looks correct. Promise-chain mutex + atomic write via temp-file rename — solid pattern, zero new deps. 14 new tests cover regression, concurrency (5 and 10 parallel appends), cleanup, and API contract. Rebased on dev (includes #483 CI timeout fix).
This was referenced Mar 22, 2026
chrislomonico
pushed a commit
to clomonico/squad
that referenced
this pull request
Mar 26, 2026
…adygaster#480) * feat: session persistence across shell restarts (bradygaster#459) Add session store module that saves and restores shell message history across shell restarts. Sessions are persisted as JSON files in .squad/sessions/ with unique IDs and safe timestamps. Changes: - New session-store.ts: createSession, saveSession, loadLatestSession, listSessions, loadSessionById functions - Auto-resume recent sessions (<24h) on shell start - Auto-save session on each message exchange - Final save on shell exit - /sessions command to list past sessions - /resume command to restore a specific session by ID prefix - 13 new tests for session store module - Package export for session-store subpath Closes bradygaster#459 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use unwrapped addMessage in onRestoreSession to avoid duplicates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
\�ppendToHistory()\ in \history-shadow.ts\ uses a read → regex insert → write pattern with no synchronization. When two concurrent agents append to the same \history.md, the last write wins and the other agent's entry is silently lost.
Root Cause
The function reads the entire file, performs a regex-based section insertion, then writes the entire file back. Between the read and write, another caller can read the same (stale) version, compute its own update, and overwrite the first caller's changes.
Fix
Zero new dependencies — pure Node.js built-ins only.
Tests
14 new tests in \ est/history-shadow.test.ts:
All 14 pass. Existing agent tests (65 tests) unaffected.
Fixes #479