Repository navigation
[Story 4.2c] File-level write serialization #376
Description
Part of #359 (Epic 4: Parallel Tool Execution).
Depends on: Story 4.2b (Core parallel dispatch). Must land before PARALLEL_TOOL_EXECUTION is promoted from false to true as default.
Context
Story 4.2b dispatches write_to_file, apply_diff, and insert_code_block as fully parallel — safe for writes to different files, but a filesystem race if two tools target the same path in one response. This story adds per-path serialization using VS Code's SequencerByKey pattern.
This story must land before PARALLEL_TOOL_EXECUTION is promoted from false to true as a default.
Developer Notes
File-level serialization in src/core/assistant-message/presentAssistantMessage.ts:
-
Move
write_to_file,apply_diff,insert_code_blockfrom the fully-parallelizable bucket into a "parallelizable with file-level serialization" bucket. -
Implement a keyed sequencer using
normalizePath(path.resolve(cline.cwd, toolArgs.path))fromsrc/utils/path.tsfor key normalization. For case-insensitive comparison on the map key, use the existingarePathsEqual()pattern (applies.toLowerCase()on Windows, case-sensitive on POSIX):const fileWriteQueues = new Map<string, Promise<void>>() function enqueueWrite(filePath: string, fn: () => Promise<void>): Promise<void> { const prev = fileWriteQueues.get(filePath) ?? Promise.resolve() const next = prev.then(fn) fileWriteQueues.set(filePath, next.catch(() => {})) // prevent queue stall on error return next }
This is VS Code's
SequencerByKeypattern. ThefileWriteQueuesmap is local to eachpresentAssistantMessageParallelcall (not shared across turns).
Logging: Add structured logs at dispatch start and end with mode (parallel/serial), tool counts, and duration. Follow the existing outputChannel.appendLine() pattern used elsewhere in the file.
Files: src/core/assistant-message/presentAssistantMessage.ts
Tests (extend src/core/assistant-message/__tests__/presentAssistantMessageParallel.spec.ts):
File-level serialization:
- Two
write_to_fileto same path → second starts only after first completes (controlled promises). - Two
write_to_fileto different paths → both start concurrently. - One
write_to_file+ oneapply_diffto same path → serialized. Different paths → concurrent. - Write error doesn't stall the queue for the same path on subsequent turns (
.catch(() => {})guard).
Acceptance Criteria
- Two write tools targeting the same file path never execute concurrently.
- Two write tools targeting different paths execute concurrently.
- Queue stall guard: a write error does not permanently block subsequent writes to the same path.
- Structured logs emitted at dispatch start and end.
Activity
[-][Story 4.2c] File-level write serialization and observability[/-][+][Story 4.2c] File-level write serialization[/+]