Bug Description
When Claude Code reaches the maximum number of turns (error_max_turns), the execution_file output is not written to disk. The execution data (messages, cost, turn count, duration) is accumulated in memory and the result object is printed to stdout, but the file at $RUNNER_TEMP/claude-execution-output.json is never created.
Root Cause
In base-action/src/run-claude-sdk.ts, the file write happens after the for await loop's try/catch block:
try {
for await (const message of query(...)) {
messages.push(message);
// ...
}
} catch (error) {
console.error("SDK execution error:", error);
throw new Error(`SDK execution error: ${error}`);
}
// This code is never reached when the SDK throws
try {
await writeFile(EXECUTION_FILE, JSON.stringify(messages, null, 2));
result.executionFile = EXECUTION_FILE;
} catch (error) {
core.warning(`Failed to write execution file: ${error}`);
}
When the SDK throws (e.g., Claude Code returned an error result: Reached maximum number of turns), the catch block re-throws before the writeFile call is reached.
Impact
- The
execution_file output is empty, so downstream steps that depend on it (e.g., extracting cost, turn count, duration, tool error counts) get no data
- The result object is printed to stdout (with
num_turns, total_cost_usd, duration_ms, subtype: "error_max_turns"), but the file is absent — consumers can't access the structured data
- This affects any workflow that uses
steps.<id>.outputs.execution_file for post-run metrics or diagnostics
Expected Behavior
The execution file should be written regardless of whether the SDK throws. The messages array is already populated with all conversation turns up to the point of failure — writing it to disk before re-throwing preserves diagnostic data.
Suggested Fix
Move the writeFile call into a finally block, or write the file in the catch block before re-throwing:
} catch (error) {
console.error("SDK execution error:", error);
// Write partial execution data before re-throwing
try {
await writeFile(EXECUTION_FILE, JSON.stringify(messages, null, 2));
result.executionFile = EXECUTION_FILE;
} catch (writeError) {
core.warning(`Failed to write execution file: ${writeError}`);
}
throw new Error(`SDK execution error: ${error}`);
}
Environment
claude-code-action@v1 (SHA: 905d4eb99ab3d43143d74fb0dcae537f29ac330a)
- Bedrock mode with
--max-turns set
Bug Description
When Claude Code reaches the maximum number of turns (
error_max_turns), theexecution_fileoutput is not written to disk. The execution data (messages, cost, turn count, duration) is accumulated in memory and the result object is printed to stdout, but the file at$RUNNER_TEMP/claude-execution-output.jsonis never created.Root Cause
In
base-action/src/run-claude-sdk.ts, the file write happens after thefor awaitloop's try/catch block:When the SDK throws (e.g.,
Claude Code returned an error result: Reached maximum number of turns), the catch block re-throws before thewriteFilecall is reached.Impact
execution_fileoutput is empty, so downstream steps that depend on it (e.g., extracting cost, turn count, duration, tool error counts) get no datanum_turns,total_cost_usd,duration_ms,subtype: "error_max_turns"), but the file is absent — consumers can't access the structured datasteps.<id>.outputs.execution_filefor post-run metrics or diagnosticsExpected Behavior
The execution file should be written regardless of whether the SDK throws. The
messagesarray is already populated with all conversation turns up to the point of failure — writing it to disk before re-throwing preserves diagnostic data.Suggested Fix
Move the
writeFilecall into afinallyblock, or write the file in thecatchblock before re-throwing:Environment
claude-code-action@v1(SHA:905d4eb99ab3d43143d74fb0dcae537f29ac330a)--max-turnsset