PicoClaw

PicoClaw can leak internal tool-call syntax into user summaries

An open PicoClaw patch targets Seahorse summaries that can expose provider-specific tool-call markup instead of a clean user-facing result. Supporting channel and model-resolution changes show the same boundary pressure: transport representation must not become public copy or redirect a model lookup.

← Back to homeOriginal source ↗

sipeed/picoclaw PR #3279 is the inspected primary source: “fix(seahorse): prevent tool-call format leakage into LLM summaries.” The two related records below were inspected as supporting context, not treated as independent confirmation.

The facts

- sipeed/picoclaw PR #3279 was created 2026-07-21T03:28:54Z and was open at inspection; its title is “fix(seahorse): prevent tool-call format leakage into LLM summaries.” - sipeed/picoclaw PR #3256 was created 2026-07-14T05:27:04Z and was closed at inspection; its title is “fix(feishu): send audio and video with native message types.” - sipeed/picoclaw PR #3254 was created 2026-07-13T11:45:54Z and was closed at inspection; its title is “fix(agent): prefer verbatim model matches over provider-alias splits when resolving refs.” - The repository reported 29751 stars, 55 open issues, default branch main, and last push 2026-07-23T18:29:33Z. - The latest tagged-release baseline checked was nightly published 2026-07-02T01:26:53Z.

What changed

sipeed/picoclaw PR #3279 reports: Related: same symptom triggered by seahorse summaries I just hit this exact class of bug — tool-call format leaking into user messages — but triggered through a different path: **seahorse's partsToReadableContent**. The seahorse variant partsToReadableContent (in pkg/seahorse/store.go) serializes tool calls as [tool_use: name, args: {...}] for FTS5 indexing. This format was also used as a fallback in two summary-generation paths (formatMessagesForSummary and truncateSummary in short_compaction.go). When summaries containing [tool_use: exec, args: {...}] were fed back into the LLM context, the model learned to mimic this format in its own responses — outputting mock tool calls as visible text content. The pipeline Fix PR incoming at: https://github.com/sipeed/picoclaw/pulls (MrTreasure:fix/seahorse-tool-call-leak) Three changes: 1. **short_compaction.go**: Skip messages with empty content in summary/truncation paths — tool calls aren't conversational content 2. **store.go**: Change FTS5 format from [tool_use: name, args: {...}] to emoji markers — the bracket-colon pattern is what LLMs latch onto 3. **agent_utils.go**: Strip residual [tool_use:...] from outbound messages as a safety net Same root cause as this

sipeed/picoclaw PR #3256 reports: Description Feishu file uploads already distinguish opus audio and mp4 video, but the send step always used the generic file message type. As a result, uploaded audio and video were delivered as downloadable files instead of native playable messages. This change maps outbound media parts to Feishu message types: audio → audio video → media other files → file The upload format and generic-file behavior remain unchanged. Type of Change [x] Bug fix (non-breaking change which fixes an issue) [ ] New feature (non-breaking change which adds functionality) [ ] Documentation update [ ] Code refactoring (no functional changes, no api changes) AI Code Generation [x] Fully AI-generated (100% AI, 0% Human) [ ] Mostly AI-generated (AI draft, Human verified/modified) [ ] Mostly Human-written (Human lead, AI assisted or none) Related Issue No linked issue. The bug was reproduced from the current main code path, and no open duplicate PR was found before submission.

sipeed/picoclaw PR #3254 reports: Description lookupModelConfigByRef (pkg/agent/model_resolution.go) resolved model references in a single pass that mixed three match types (verbatim model string, bare model ID, and split-based ModelKey). Because of that, an earlier model_list entry could win via a provider-alias split against a later entry that matches the reference **verbatim**. Concrete failure (from #3252): with referencing google/gemini-3.1-flash-lite-preview resolved to the **gemini** entry instead of the openrouter one: the google → gemini alias split turns the raw ref into the key gemini/gemini-3.1-flash-lite-preview, which collides with the first entry before the loop ever reaches the verbatim match on the second entry. The wrong provider/model is then used downstream. This PR restructures the lookup into explicit match tiers, strongest first: 1. model_name lookup (unchanged) 2. verbatim model string match 3. bare model ID match (with the existing default-provider guard) 4. split-based ModelKey match This also brings agent-side resolution in line with the gateway, which already matches in tiered passes (findSignatureModelConfigs in web/backend/api/gateway.go: ModelName → verbatim Model → bare ref → provider

Why it matters

A tool protocol is an internal language. Once its delimiters leak into summaries, users cannot tell whether they are reading a result, an instruction or serialization debris, and downstream channels may render it differently. This is an operator boundary: persisted state, execution ownership and visible controls must describe the same event. A workaround that broadens access, deletes state or hides the symptom would trade a visible defect for an unmeasured one.

Current

The primary record was open when captured on 2026-07-29. The release baseline was nightly published 2026-07-02T01:26:53Z. Current repository metadata, pull requests, issues, release records, Hacker News discovery results and the rendered ClawCharts row were checked. ClawCharts selected the subject; it did not prove the claim.

Evidence

The primary URL is https://github.com/sipeed/picoclaw/pull/3279. Supporting records are https://github.com/sipeed/picoclaw/pull/3256 and https://github.com/sipeed/picoclaw/pull/3254. Their source bodies, timestamps and states were captured in the daily evidence bundle. Test counts, reproductions and deployment observations remain attributed to their authors unless explicitly identified as independently rerun.

Source boundary

Open work is described as open, closed work as closed, and operator reports as reports. A pull request is evidence of proposed or reviewed direction, not proof of a shipped release. Issue closure would not by itself prove a deployed fix. Search residue, package mirrors and historically published source spines were excluded.

Operator take

Test malformed, partial and nested tool calls across providers; sanitize at the semantic event boundary rather than by deleting suspicious strings at the final renderer. Preserve a before-state receipt, make the smallest reversible change, and verify the original failure independently after intervention.

Caveat

Public project records are mutable. Status, scope and evidence can change after publication. This brief records the inspected state and does not authorize changes to a reader’s deployment.

Source inspected; source state, environment and release boundary remain explicit.