Environment
- codewhale v0.9.11 (Linux), run as codewhale serve --http behind a systemd unit
- Provider: OpenAI-compatible endpoint (strict serde schema; rejects empty required fields)
- Client: official integrations/feishu-bridge → /v1/threads/{id}/turns
Symptom
A thread that contains at least one completed tool call works fine while the runtime process stays alive. After the runtime process restarts, any new turn on that thread fails with:
Invalid request (400): Provider API error: Failed to deserialize the JSON body into the target type:
messages[2]: missing field name at line 1 column 1327
Fresh threads (no tool history) work fine after restart. The failure reproduces reliably.
Reproduction
- Start codewhale serve --http (any workspace with a tool-capable config).
- Create a thread, run a turn that invokes a tool (e.g. read), let it complete.
- Restart the serve --http process.
- Send another turn on the same thread → 400 missing field name on messages[2].
Captured request body from the failing turn shows the replayed assistant.tool_calls entry is an empty shell:
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "",
"type": "function",
"function": { "name": "", "arguments": "null" }
}
]
}
Root cause (evidence)
Tool metadata (tool id/name/input) is present in the item.started runtime event:
{ "event": "item.started", ...,
"payload": { "item": {...}, "tool": { "id": "call_00_...", "name": "read", "input": {"path": "README.md"} } } }
but the persisted item.completed snapshot only carries summary/detail — the tool fields are dropped. On restart, history is rebuilt from those snapshots, so assistant.tool_calls is serialized with empty id/name and arguments: "null". OpenAI-compatible endpoints that validate required fields then reject the request.
Impact
Any long-lived thread that used tools becomes unusable after every runtime restart (/new is the only workaround). This affects chat bridges (Feishu/Lark/Telegram) that reuse threads across restarts.
Suggested fix
Persist the tool metadata (id, name, input) on the tool-call item snapshot (or reconstruct tool_calls from the event stream on replay) so a restart does not produce empty name/id/arguments.
Environment
Symptom
A thread that contains at least one completed tool call works fine while the runtime process stays alive. After the runtime process restarts, any new turn on that thread fails with:
Invalid request (400): Provider API error: Failed to deserialize the JSON body into the target type:
messages[2]: missing field
nameat line 1 column 1327Fresh threads (no tool history) work fine after restart. The failure reproduces reliably.
Reproduction
Captured request body from the failing turn shows the replayed assistant.tool_calls entry is an empty shell:
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "",
"type": "function",
"function": { "name": "", "arguments": "null" }
}
]
}
Root cause (evidence)
Tool metadata (tool id/name/input) is present in the item.started runtime event:
{ "event": "item.started", ...,
"payload": { "item": {...}, "tool": { "id": "call_00_...", "name": "read", "input": {"path": "README.md"} } } }
but the persisted item.completed snapshot only carries summary/detail — the tool fields are dropped. On restart, history is rebuilt from those snapshots, so assistant.tool_calls is serialized with empty id/name and arguments: "null". OpenAI-compatible endpoints that validate required fields then reject the request.
Impact
Any long-lived thread that used tools becomes unusable after every runtime restart (/new is the only workaround). This affects chat bridges (Feishu/Lark/Telegram) that reuse threads across restarts.
Suggested fix
Persist the tool metadata (id, name, input) on the tool-call item snapshot (or reconstruct tool_calls from the event stream on replay) so a restart does not produce empty name/id/arguments.