Skip to content

Conversation permanently bricked when a model tool call has invalid JSON arguments (no ingest validation, no 400 self-heal) #56

Description

@sroerick

Environment: Autolith 0.45.0 release binary (x86_64-openbsd); OpenAI-compatible provider (Synthetic, api.synthetic.new), model syn:large:text.

Summary: When the model emits a function_call whose arguments string is not valid JSON, Autolith persists the PROVIDER-ITEM verbatim. Every subsequent request replays it, and the backend hard-rejects with a non-retryable 400 - Assistant tool call function.arguments must be valid JSON. The conversation is then permanently unusable: every new user message (including automated heartbeats) dies instantly with no assistant output, and kill+resume does NOT help because the poison is on disk.

Observed in production after the model glitched mid-generation while producing a deeply-nested-escaping shell command: it spliced two JSON objects into one arguments string, and also hallucinated the tool name (runt_5_shell__run; real tool is run). Autolith correctly recorded an unknown-tool error output for the call, but the invalid arguments stayed in history.

Evidence (sanitized):

Persisted item (one line of the segment sexp, Lisp-escaped wire JSON):
{"type":"function_call","call_id":"call_581...","name":"runt_5_shell__run","arguments":"{\"command\": \"D=/home/...; grep -ob '\"role\":\"user\"' $D/000000000000{\"command\": \"D=/home/...\"}","namespace":"shell"} - invalid JSON at char 115 (a second {"command": ... literal spliced in mid-string).

Following PROVIDER records:
(:FAILURE (:MESSAGE "... 400 Assistant tool call function.arguments must be valid JSON." :STATUS 400 :RETRYABLE-P NIL))
for the next request AND for every later user message / heartbeat turn.

Root-cause confirmation: we rewrote only that one record's arguments to valid JSON in the segment file (backup taken, sessions restarted) and the conversation immediately recovered - 6/6 subsequent provider requests succeeded, including tool calls. The single malformed item was the poison.

Repro: any OpenAI-compatible provider that validates tool-call arguments. Get the model to emit malformed arguments (heavy quote-escaping in shell commands triggers it), or hand-insert such a function_call item into a conversation segment. Next request -> 400, forever; resuming the same conversation reproduces.

Suggested fixes:

  1. Validate arguments parses as JSON at ingest; if not, sanitize on persist and feed a synthetic tool-result error ("arguments were not valid JSON") so the turn continues and history stays replayable.
  2. On a 400 matching this message, auto-heal (scan replayed history for function_call items with unparseable arguments, repair/quarantine, retry) - or at minimum offer a /repair command. Today the only recovery is hand-editing sexp files.
  3. Consider validating the tool name against the registry at ingest as well.
  4. Minor: surface the last turn error to integrations - our XMPP bridge could only show "(no text output)", which hid a total conversation loss for hours

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions