Summary
The example in base-action/README.md for parsing execution_file walks the array looking for e.role === 'assistant'. That field doesn't exist at the top level of any event the action currently emits, so the example matches zero events and silently falls through.
Where the example is
base-action/README.md (the "Claude Code Review" / "Extract and Comment PR Review" example):
const executionLog = JSON.parse(fs.readFileSync(executionFile, 'utf8'));
let review = '';
for (let i = executionLog.length - 1; i >= 0; i--) {
if (executionLog[i].role === 'assistant') { // <-- never matches
review = executionLog[i].content;
break;
}
}
Why it doesn't work
base-action/src/run-claude-sdk.ts writes the file as:
await writeFile(EXECUTION_FILE, JSON.stringify(messages, null, 2));
…where messages: SDKMessage[] from @anthropic-ai/claude-agent-sdk. SDK events use type at the top level; role only exists nested inside message:
So e.role is always undefined and the loop falls through. Downstream steps then post nothing (or in my case, log "no assistant message found") and the user can't tell the example is wrong without reading the source.
Reproduction
Minimal workflow that follows the README example verbatim:
- id: claude
uses: anthropics/claude-code-action@v1
with:
use_bedrock: "true" # or anthropic_api_key for the 1P provider
prompt: "Reply with: hello"
claude_args: --max-turns 1
- uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
const log = JSON.parse(fs.readFileSync('${{ steps.claude.outputs.execution_file }}', 'utf8'));
for (let i = log.length - 1; i >= 0; i--) {
if (log[i].role === 'assistant') {
core.info('found: ' + log[i].content);
return;
}
}
core.info('no assistant message found'); // <-- this is what fires on every run
core.info('no assistant message found') fires on every successful run.
Suggested fix (docs)
Update the example to match the actual SDK shape. The canonical "final answer" event is type: "result" with a plain result string:
const log = JSON.parse(fs.readFileSync(executionFile, 'utf8'));
// Preferred: the SDK's documented final-answer event.
const result = log.find(e => e && e.type === 'result' && typeof e.result === 'string');
let review = result ? result.result : '';
// Fallback: last assistant text block (covers runs that abort before `result`).
if (!review) {
for (let i = log.length - 1; i >= 0; i--) {
const e = log[i];
if (e && e.type === 'assistant' && e.message && Array.isArray(e.message.content)) {
review = e.message.content
.filter(c => c && c.type === 'text' && typeof c.text === 'string')
.map(c => c.text)
.join('\n');
if (review) break;
}
}
}
It might also be worth adding a one-line pointer to --json-schema + outputs.structured_output for users who want a typed surface and don't want to parse the file at all.
Environment
- Action:
anthropics/claude-code-action@v1
- Provider: AWS Bedrock (
use_bedrock: "true"), but the file shape is provider-independent (it's whatever the SDK emits).
- Confirmed against
base-action/src/run-claude-sdk.ts at main.
Summary
The example in
base-action/README.mdfor parsingexecution_filewalks the array looking fore.role === 'assistant'. That field doesn't exist at the top level of any event the action currently emits, so the example matches zero events and silently falls through.Where the example is
base-action/README.md(the "Claude Code Review" / "Extract and Comment PR Review" example):Why it doesn't work
base-action/src/run-claude-sdk.tswrites the file as:…where
messages: SDKMessage[]from@anthropic-ai/claude-agent-sdk. SDK events usetypeat the top level;roleonly exists nested insidemessage:[ { "type": "system", "subtype": "init", "session_id": "..." }, { "type": "user", "message": { "role": "user", "content": [ ... ] } }, { "type": "assistant", "message": { "role": "assistant", "content": [ { "type": "text", "text": "..." } ] } }, { "type": "result", "subtype": "success", "result": "<final text>", "is_error": false, "total_cost_usd": ..., ... } ]So
e.roleis alwaysundefinedand the loop falls through. Downstream steps then post nothing (or in my case, log "no assistant message found") and the user can't tell the example is wrong without reading the source.Reproduction
Minimal workflow that follows the README example verbatim:
core.info('no assistant message found')fires on every successful run.Suggested fix (docs)
Update the example to match the actual SDK shape. The canonical "final answer" event is
type: "result"with a plainresultstring:It might also be worth adding a one-line pointer to
--json-schema+outputs.structured_outputfor users who want a typed surface and don't want to parse the file at all.Environment
anthropics/claude-code-action@v1use_bedrock: "true"), but the file shape is provider-independent (it's whatever the SDK emits).base-action/src/run-claude-sdk.tsatmain.