Skip to content

Commit 84d317e

Browse files
authored
fix: break SDK iterator after result message to prevent hang (#1339)
In some workflow contexts — reliably reproducible for us on pull_request-triggered runs of this action — the Claude Agent SDK query() async iterator does not close after the terminal result message is emitted. The for-await loop in runClaudeWithSdk blocks indefinitely after Claude has finished its work, until the workflow's timeout-minutes cap kills the job. Symptoms observed in production (4× in our scan-reviewer workflow): - Claude completes successfully: SDK emits { type: "result", subtype: "success", ... } with the cost / turns / duration set. - The action then sits with zero log output for the rest of timeout-minutes (we measured 18-19 min of dead time after result). - The job is cancelled at timeout. writeExecutionFile is never called → no claude-execution-output.json → cost-tracker and other post-steps see nothing. - Run shows as cancelled, even though Claude did its work and any verdict it posted via gh tools already landed. Author-mode (workflow_dispatch) runs from the same codebase terminate cleanly the same day, so the hang is specific to certain event triggers. By SDK contract the result message is terminal — no further messages follow. Break out of the loop immediately after capturing it, regardless of whether the upstream iterator ever closes. If the SDK is later fixed to close cleanly in all contexts, this break becomes a no-op.
1 parent cd59d5d commit 84d317e

1 file changed

Lines changed: 10 additions & 0 deletions

File tree

base-action/src/run-claude-sdk.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,16 @@ export async function runClaudeWithSdk(
167167

168168
if (message.type === "result") {
169169
resultMessage = message as SDKResultMessage;
170+
// The SDK's query() iterator should close itself after the
171+
// result message, but in some workflow contexts (notably
172+
// pull_request-triggered runs) it stays open indefinitely and
173+
// the for-await hangs until the workflow's timeout-minutes
174+
// kills the job. This causes the action to "succeed" inside
175+
// Claude (verdict posted, $cost recorded) but be reported as
176+
// cancelled with no execution-output.json written. Break
177+
// explicitly: by SDK contract no further messages follow a
178+
// result, so the break is safe.
179+
break;
170180
}
171181
}
172182
} catch (error) {

0 commit comments

Comments
 (0)