Skip to content

fix(file-store): add write lock to prevent concurrent save race condition#1

Merged
matagaralph merged 1 commit into
mainfrom
fix/file-store-race-condition
Apr 17, 2026
Merged

fix(file-store): add write lock to prevent concurrent save race condition#1
matagaralph merged 1 commit into
mainfrom
fix/file-store-race-condition

Conversation

@matagaralph
Copy link
Copy Markdown
Owner

@matagaralph matagaralph commented Apr 17, 2026

The FileTokenStore had a race condition where concurrent saveToken or deleteToken calls could interleave their read-mutate-write cycles, causing one write to clobber another.

Fixed by introducing a promise-based write lock via withLock, which serializes all mutating operations without any external dependencies. Read operations are unaffected.

Also tightened up findToken in both FileTokenStore and RedisTokenStore to check expiresAt and return null for expired tokens rather than leaking stale data to the OAuth layer.

Copilot AI review requested due to automatic review settings April 17, 2026 13:28
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an in-process write serialization mechanism to FileTokenStore to mitigate concurrent saveToken / deleteToken races when persisting tokens to disk.

Changes:

  • Introduce a writeLock promise chain in FileTokenStore.
  • Add a withLock() helper and wrap saveToken() / deleteToken() operations with it.

Comment on lines 36 to 37
const tokens = await this.read();
return tokens.find((t) => t.clientId === clientId && t.orgId === orgId) ?? null;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new write lock only serializes save/delete, but findToken still reads the file concurrently with writeFile. Since writeFile truncates and rewrites the JSON, a concurrent read can observe partial contents and JSON.parse will throw. Consider either (a) using the same lock for findToken/read operations, or (b) making writes atomic (write to a temp file and rename) so reads are always valid JSON.

Suggested change
const tokens = await this.read();
return tokens.find((t) => t.clientId === clientId && t.orgId === orgId) ?? null;
return this.withLock(async () => {
const tokens = await this.read();
return tokens.find((t) => t.clientId === clientId && t.orgId === orgId) ?? null;
});

Copilot uses AI. Check for mistakes.
@matagaralph matagaralph merged commit 459b175 into main Apr 17, 2026
10 checks passed
@matagaralph matagaralph deleted the fix/file-store-race-condition branch April 27, 2026 07:59
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.

2 participants