Skip to content

bug: Agent server stream-json parser crashes on non-dict JSON lines #151

Description

@vybe

Summary

The agent server's headless task stdout parser crashes when Claude Code emits a stream-json line that is a valid JSON string literal (not an object). This causes the entire execution to fail with "Task returned empty response" — discarding all work done during the session. Observed on a scheduled task that ran for ~9 minutes before the crash.

Component

Agent Server / claude_code.py / Headless Task stdout parser

Priority

P2

Error

ERROR:agent_server.services.claude_code:[Headless Task] Error reading stdout: 'str' object has no attribute 'get'

Downstream effect:

ERROR:services.task_execution_service:[TaskExecService] Failed to execute task on <agent>: Task returned empty response

Location

  • File: src/agent-server/agent_server/services/claude_code.py
  • Line: ~883 (in the _run_headless_with_streaming method)
  • Function: stdout reading loop inside _run_headless_with_streaming

Root Cause

The stdout reading loop does:

raw_msg = json.loads(line.strip())       # line 877
raw_msg = sanitize_dict(raw_msg)          # line 879
...
if raw_msg.get("type") == "init" ...      # line 883

json.loads() can successfully parse a JSON string literal like "some text" into a Python str. The code then calls .get() on that string, which raises AttributeError: 'str' object has no attribute 'get'.

This AttributeError is caught by the broad except Exception as e at ~line 912, which logs the error and exits the stdout reading loop entirely — meaning all subsequent output (including the result message with cost/session data) is never processed. The execution is then recorded as failed with null cost, null session_id, and no response.

Reproduction Steps

  1. Run a scheduled task that uses context: fork with a subagent (or any task where Claude Code emits a non-object JSON line in stream-json mode)
  2. Observe the execution fails with "Task returned empty response"
  3. Agent container logs show: Error reading stdout: 'str' object has no attribute 'get'

Suggested Fix

Add a type check after json.loads() to skip non-dict lines:

try:
    raw_msg = json.loads(line.strip())
    if not isinstance(raw_msg, dict):
        # stream-json can emit string literals; skip them
        continue
    raw_msg = sanitize_dict(raw_msg)
    raw_messages.append(raw_msg)
    ...

The same check should be added in process_stream_line() (line ~276) for defense in depth, though the primary fix in the stdout loop would prevent the crash.

Additionally, sanitize_dict() should handle non-dict input gracefully.

Environment

  • Trinity version: 0523ccd
  • Agent server: built into agent container image

Related

  • src/agent-server/agent_server/services/claude_code.py — both _run_headless_with_streaming and process_stream_line
  • parse_stream_json_output() (line 124) has the same pattern but already returns early on JSONDecodeError; it still calls .get() without checking type

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions