Skip to content

Commit 0d60956

Browse files
anandgupta42claude
andauthored
fix: use atomic writes in FileExporter.export() to prevent corrupted trace files (#646)
`FileExporter.export()` used `fs.writeFile` directly, while the snapshot mechanism used atomic writes (tmp + rename). When concurrent sessions wrote to the same trace file, a snapshot rename could race with a non-atomic writeFile, producing truncated JSON ("Unterminated string" parse error). Also adds temp file cleanup on failure, matching the existing `snapshot()` pattern. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 448a8cb commit 0d60956

File tree

1 file changed

+7
-1
lines changed
  • packages/opencode/src/altimate/observability

1 file changed

+7
-1
lines changed

packages/opencode/src/altimate/observability/tracing.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,19 +172,25 @@ export class FileExporter implements TraceExporter {
172172
}
173173

174174
async export(trace: TraceFile): Promise<string | undefined> {
175+
let tmpPath: string | undefined
175176
try {
176177
await fs.mkdir(this.dir, { recursive: true })
177178
// Sanitize sessionId for safe file name (defense-in-depth — also sanitized in Trace)
178179
const safeId = (trace.sessionId ?? "unknown").replace(/[/\\.:]/g, "_") || "unknown"
179180
const filePath = path.join(this.dir, `${safeId}.json`)
180-
await fs.writeFile(filePath, JSON.stringify(trace, null, 2))
181+
// Atomic write: write to temp file, then rename — prevents partial reads
182+
// when concurrent snapshots or exports target the same file
183+
tmpPath = filePath + `.tmp.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`
184+
await fs.writeFile(tmpPath, JSON.stringify(trace, null, 2))
185+
await fs.rename(tmpPath, filePath)
181186

182187
if (this.maxFiles > 0) {
183188
this.pruneOldTraces().catch(() => {})
184189
}
185190

186191
return filePath
187192
} catch {
193+
if (tmpPath) fs.unlink(tmpPath).catch(() => {})
188194
return undefined
189195
}
190196
}

0 commit comments

Comments
 (0)