fix(governance): move TMP_PATH write inside lock in GoalManager.save() (PKT-563) - #454
Open
bugsyhewitt wants to merge 1 commit into
Open
fix(governance): move TMP_PATH write inside lock in GoalManager.save() (PKT-563)#454bugsyhewitt wants to merge 1 commit into
bugsyhewitt wants to merge 1 commit into
Conversation
…) (PKT-563) Concurrent save() calls shared TMP_PATH before acquireLock(), allowing a second caller to overwrite the first's tmp data before the first renames it — producing silent data inversion and ENOENT on the losing caller. Move writeFileSync(TMP_PATH, ...) inside the acquireLock/releaseLock critical section so each save() owns TMP_PATH for the full write→rename window. Adds three concurrent-save tests covering: both saves fulfill, on-disk self-consistency, and no TMP_PATH orphan. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.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
GoalManager.save()wrote to sharedTMP_PATHbefore acquiring the lock. Two concurrent callers both ranwriteFileSync(TMP_PATH, ...)before either held the lock, so whichever caller won the lock renamed a TMP file containing the other caller's data — silent data inversion. The losing caller then hit ENOENT on its rename (TMP was gone).writeFileSync(TMP_PATH, ...)inside theacquireLock/releaseLockcritical section (2 LOC moved). Write + rename now execute atomically under the lock with no yield points between them.TDD evidence
Failing-first (buggy code):
After fix:
Tests 44 passed (44)Ship-gate
pnpm test(vitest 2186 passed + 46 skipped, pytest 1241 passed + 125 skipped) — exit 0.Files changed
src/alienclaw/governance/common/goal-manager.ts— 2-LOC move + comment corrected fromtmp → locktolock → tmp → rename → releasetest/governance/common/goal-manager.test.ts— section 15 (3 new concurrent-save tests)Related
getReadySubGoals/getReadyCampaignsundefineddependsOn)🤖 Generated with Claude Code