Skip to content

sidecall: read the result event out of a verbose Claude Code reply - #141

Closed
fchengyan (fcy222fcy) wants to merge 1 commit into
microsoft:mainfrom
fcy222fcy:fix/sidecall-verbose-array
Closed

fchengyan (fcy222fcy) wants to merge 1 commit into
microsoft:mainfrom
fcy222fcy:fix/sidecall-verbose-array

Conversation

@fcy222fcy

Copy link
Copy Markdown

Summary

Fixes #131. With "verbose": true in the user's Claude Code settings, every Claude Code side call failed, and the micro-skills proposer's unguarded runner call aborted the whole fold.

Two defects, both in the #131 report:

  1. claude_call mis-parsed the reply. Under -p --output-format json, a verbose Claude Code prints a JSON array of stream events (system, assistant, ..., result), not the bare result object. json.loads then data.get("result") raised AttributeError: 'list' object has no attribute 'get'.
  2. Distiller._maybe_propose called its runner unguarded. Unlike the loop guard's judge (loopguard/judge.py catches runner exceptions and records runner-error:...), the proposer let the exception escape, so the fold failed: the session stayed pending, got no ledger row, and was re-folded — with another paid Haiku call — on a later eligible SessionStart event.

What changed

  • tokeneconomizer/plugin/sidecall.py — new _claude_json_result(stdout) normalizes what the CLI printed: a list's last type == "result" event, the bare object as before, or {}. claude_call and nothing else changes shape: the usage/modelUsage accounting and the _no_reply path are untouched.
  • tokeneconomizer/microskills/distiller.py — _maybe_propose guards its runner like the judge does: a failed side call skips the proposal instead of aborting the fold, and backs off one cadence (last_propose = now) so a broken runner is not paid again on every tick.
  • tests/test_sidecall_verbose.py — three regression tests (below).

I went with shape-accepting rather than the report's --settings '{"verbose":false}' suggestion: it needs no capability probe of --settings, and it keeps working for a CLI that prints the array for any other reason.

Verification

Red/green with the subprocess faked the way test_live_core.py fakes it (the reporter's real-CLI repro is not available on this machine):

  • verbose-array reply with a result event → before: AttributeError: 'list' object has no attribute 'get' at sidecall.py:235; after: returns ("OK", eff) with usage charged.
  • verbose-array reply with no result event → before: same AttributeError; after: comes back empty through the _no_reply path.
  • a runner that raises in _maybe_propose → before: the exception escaped and aborted the fold; after: the proposal is skipped and last_propose backs off.

Reverse control: git stash of the two source files (tests kept) turns all three red; restoring turns them green. Full offline suite (python -m pytest tests -q) is green on Python 3.11 / Windows, including test_live_core.py and test_live_repos.py.

AI disclosure

This fix was authored with AI assistance (Claude agent, at the direction of fcy222fcy).

…icrosoft#131)

With "verbose": true in the user's Claude Code settings, -p --output-format json
prints a JSON array of stream events instead of the bare result object, so
claude_call's data.get("result") raised AttributeError ('list' object has no
attribute 'get'). The micro-skills proposer called its runner unguarded, so the
same exception aborted the whole fold: the session stayed pending and was
re-folded, with another paid call, on a later eligible tick.

- claude_call now parses either shape: a list's last type=="result" event, or
  the bare object as before (microsoft#131)
- Distiller._maybe_propose guards its runner like the loop guard's judge does:
  a failed side call skips the proposal and backs off one cadence instead of
  aborting the fold (microsoft#131)
@fcy222fcy

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@SakshamUboweja

Copy link
Copy Markdown
Collaborator

Thanks for this, and for the clear red/green write-up. #134 fixes #131 the same way and also logs the failed proposer call to errors.log, so I’m merging that one and closing this as a duplicate. Your handling of a non-object JSON reply (e.g. null) is a nice touch that #134 doesn’t have. A small follow-up PR on top of #134 for that case would be welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Claude Code side calls fail when the user settings have verbose: true, and a failed proposer call aborts the fold

2 participants