Windows: background agent .output files always 0 bytes (symlink fallback)
Bug Description
On Windows, background agent output files (.output JSONL transcripts) remain 0 bytes for the entire agent lifecycle and after completion. The completion notification returns results correctly, but the output file is never populated.
This is NOT the Git 2.53.0 bash stdout issue (though that's related). This is specific to background agent transcript files.
Environment
- Windows 11 Pro 10.0.26200
- Claude Code CLI (latest, also reproduced on prior versions)
- PowerShell AND Git Bash (MINGW64) — both affected
CLAUDE_CODE_TMPDIRremapped to~/.claude/tmp— no change (rules out temp dir permissions)
Root Cause (traced through source)
The bug is in utils/task/diskOutput.ts:
initTaskOutputAsSymlink()(line ~438) tries to create a symlink from the.outputfile to the agent's.jsonltranscript- Windows symlinks require admin/developer privileges — the
fs.symlink()call fails - The fallback (line ~447) calls
initTaskOutput()which creates an empty 0-byte file - The agent's conversation is written to the
.jsonltranscript via the project backend (recordSidechainTranscript()intools/AgentTool/runAgent.ts) - The
.outputfile has no connection to the.jsonlfile after the symlink fails — it stays 0 bytes permanently
Why Bash Works But Agents Don't
| Component | Mechanism | Works on Windows? |
|:--|:--|:--|
| Bash tool (utils/Shell.ts) | Opens file with fs.openSync(), passes fd via stdio[1] — child writes directly to file handle | Yes — OS-level fd redirect |
| Background agents (bridge/sessionRunner.ts) | stdio: ['pipe', 'pipe', 'pipe'] — parent reads via readline, writes to project backend separately | No — relies on symlink .output → .jsonl |
The Bash tool bypasses the pipe entirely by giving the child process a file descriptor. Background agents use pipes + a symlink, and the symlink is the part that breaks on Windows.
Proposed Fix
In diskOutput.ts, replace the symlink fallback with one of:
- Hard link (
fs.linkSync) — works on Windows without admin privileges, same semantics as symlink for read purposes - Junction (
fs.symlinkSyncwith'junction'type) — Windows junctions work without elevation for directories - Periodic copy/tail — write
.outputfrom the same source that feeds the.jsonl - Direct fd redirect — match the Bash tool pattern: open the
.outputfile, pass fd to the child process stdio
Option 1 (hard link) is the simplest one-line fix:
// Current (fails on Windows without admin):
fs.symlinkSync(targetPath, outputPath);
// Fix:
try {
fs.linkSync(targetPath, outputPath); // hard link works without admin on Windows
} catch {
fs.symlinkSync(targetPath, outputPath); // fall back to symlink on Unix
}
Note: hard links require both paths on the same volume, which they are (both under the temp/project directory).
Related Issues
- #17147 — Background agents output files remain empty
- #14521 — Background agents cannot write files with
run_in_background=true - #21352 — Agents complete but output files are empty
- #32252 — Background tasks report "completed" but output files missing or empty
- #21915 — Bash tool produces no output on Windows (separate Git 2.53.0 issue)
Reproduction
- Run Claude Code on Windows (any shell)
- Spawn a background agent:
Agent({ prompt: "echo hello", run_in_background: true }) - Check the output file path returned in the launch message
- File is 0 bytes during execution AND after completion
- The completion notification correctly returns the agent's result (proving the agent ran fine)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗