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_block from the fully-parallelizable bucket into a "parallelizable with file-level serialization" bucket.
-
Implement a keyed sequencer using normalizePath(path.resolve(cline.cwd, toolArgs.path)) from src/utils/path.ts for key normalization. For case-insensitive comparison on the map key, use the existing arePathsEqual() 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 SequencerByKey pattern. The fileWriteQueues map is local to each presentAssistantMessageParallel call (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_file to same path → second starts only after first completes (controlled promises).
- Two
write_to_file to different paths → both start concurrently.
- One
write_to_file + one apply_diff to 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.
Part of #359 (Epic 4: Parallel Tool Execution).
Depends on: Story 4.2b (Core parallel dispatch). Must land before
PARALLEL_TOOL_EXECUTIONis promoted fromfalsetotrueas default.Context
Story 4.2b dispatches
write_to_file,apply_diff, andinsert_code_blockas 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'sSequencerByKeypattern.This story must land before
PARALLEL_TOOL_EXECUTIONis promoted fromfalsetotrueas 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):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 existingoutputChannel.appendLine()pattern used elsewhere in the file.Files:
src/core/assistant-message/presentAssistantMessage.tsTests (extend
src/core/assistant-message/__tests__/presentAssistantMessageParallel.spec.ts):File-level serialization:
write_to_fileto same path → second starts only after first completes (controlled promises).write_to_fileto different paths → both start concurrently.write_to_file+ oneapply_diffto same path → serialized. Different paths → concurrent..catch(() => {})guard).Acceptance Criteria