Summary
walkie agent <channel> --cli claude posts raw Claude-CLI JSON to the channel instead of the assistant's reply text. Other agents (and humans) receive a blob like:
claude-bot: [{"type":"system","subtype":"init","cwd":"...","session_id":"...","tools":[...]}, ...]
The --cli codex path is unaffected — only the Claude adapter is broken.
Environment
- walkie:
1.5.0 (installed from github:vikasprogrammer/walkie)
- Node: v22.x
claude CLI: current release (Claude Code)
Repro
- Terminal A:
walkie agent demo:secret --cli claude --name claude-bot
- Terminal B:
walkie agent demo:secret --cli codex --name codex-bot
- Seed:
walkie send demo:secret "introduce yourselves"
codex-bot replies in natural language. claude-bot emits the raw JSON event stream instead of text, and re-fires on subsequent messages.
Root cause
In runClaude() (bin/walkie.js), walkie calls:
const args = ['-p', prompt, '--output-format', 'json']
and then expects an object with a top-level .result, parsing line by line:
const out = { text: stdout, sessionId: null } // defaults to RAW stdout
const lines = stdout.split('\n').filter(l => l.trim())
for (let i = lines.length - 1; i >= 0; i--) {
try {
const obj = JSON.parse(lines[i])
if (obj.session_id) out.sessionId = obj.session_id
if (obj.result !== undefined) { out.text = obj.result; break }
} catch {}
}
But the current Claude CLI's -p --output-format json returns a single-line JSON array of events — not an object:
$ claude -p "say PONG" --output-format json | node -e '...'
typeof: array len 4
has .result: false
elem types: system,rate_limit_event,assistant,result
So:
- there is only one line (the whole array on one line),
JSON.parse(line) yields an Array, on which .result / .session_id are undefined,
- the loop never overrides
out.text, so it falls back to the raw stdout dump.
The reply text actually lives in the array element with type === "result" (its .result field), and session_id is on the system/init and result events.
Suggested fix
Parse the whole stdout, handle the array-of-events shape, and keep fallbacks for the legacy single-object and NDJSON/stream-json shapes:
const stdout = (result.stdout || '').trim()
const out = { text: stdout, sessionId: null }
const applyEvent = (obj) => {
if (!obj || typeof obj !== 'object') return
if (obj.session_id) out.sessionId = obj.session_id
if (obj.type === 'result' && typeof obj.result === 'string') out.text = obj.result
else if (obj.result !== undefined) out.text = obj.result
}
let whole
try { whole = JSON.parse(stdout) } catch {}
if (Array.isArray(whole)) whole.forEach(applyEvent)
else if (whole && typeof whole === 'object') applyEvent(whole)
else for (const line of stdout.split('\n')) {
try { applyEvent(JSON.parse(line)) } catch {}
}
return out
I have a patch + PR ready to submit for this.
Secondary observations (not addressed by the above patch)
Correction (edited): an earlier version of this section claimed there was no loop guard and that agents react to system join events. That was wrong — see the corrected note below. (Details in the comment thread.)
- Loop guard is present but soft: the
agent loop already ignores its own + system messages and does @mention filtering, and caps consecutive exchanges with the same sender (MAX_CONSECUTIVE = 10) before pausing. So two agents do not loop indefinitely. The remaining concern is just that 10 back-and-forth turns is still ~10 model calls per side before the pause kicks in, and the cap is per-consecutive-same-sender — so it's worth noting as a cost/tuning consideration, not a missing feature.
- Duplicate delivery: during testing, a single
send was occasionally received twice by a peer. May warrant message-id dedup.
Happy to open follow-up issues for these if useful.
Summary
walkie agent <channel> --cli claudeposts raw Claude-CLI JSON to the channel instead of the assistant's reply text. Other agents (and humans) receive a blob like:The
--cli codexpath is unaffected — only the Claude adapter is broken.Environment
1.5.0(installed fromgithub:vikasprogrammer/walkie)claudeCLI: current release (Claude Code)Repro
walkie agent demo:secret --cli claude --name claude-botwalkie agent demo:secret --cli codex --name codex-botwalkie send demo:secret "introduce yourselves"codex-botreplies in natural language.claude-botemits the raw JSON event stream instead of text, and re-fires on subsequent messages.Root cause
In
runClaude()(bin/walkie.js), walkie calls:and then expects an object with a top-level
.result, parsing line by line:But the current Claude CLI's
-p --output-format jsonreturns a single-line JSON array of events — not an object:So:
JSON.parse(line)yields an Array, on which.result/.session_idareundefined,out.text, so it falls back to the raw stdout dump.The reply text actually lives in the array element with
type === "result"(its.resultfield), andsession_idis on thesystem/initandresultevents.Suggested fix
Parse the whole stdout, handle the array-of-events shape, and keep fallbacks for the legacy single-object and NDJSON/stream-json shapes:
I have a patch + PR ready to submit for this.
Secondary observations (not addressed by the above patch)
agentloop already ignores its own +systemmessages and does@mentionfiltering, and caps consecutive exchanges with the same sender (MAX_CONSECUTIVE = 10) before pausing. So two agents do not loop indefinitely. The remaining concern is just that 10 back-and-forth turns is still ~10 model calls per side before the pause kicks in, and the cap is per-consecutive-same-sender — so it's worth noting as a cost/tuning consideration, not a missing feature.sendwas occasionally received twice by a peer. May warrant message-id dedup.Happy to open follow-up issues for these if useful.