Skip to content

[BUG] convert.js:371 throws when systemPrompt is an array (subagent spawns fail on opus-5 / sonnet-5) #201

Description

@tomolom

Describe the bug

convertToAnthropic in dist/convert.js:371 calls context.systemPrompt?.trim(). When the caller supplies systemPrompt as an array rather than a string, ?. does not short-circuit (the value is not nullish) and the call throws:

[anthropic/claude-sonnet-5] context.systemPrompt?.trim is not a function.
(In 'context.systemPrompt?.trim()', 'context.systemPrompt?.trim' is undefined)

Under Oh My Pi, every subagent spawn I tried failed at request time before any HTTP call. Measured on claude-opus-5 and claude-sonnet-5 only — I did not test the claude-opus-4-5, claude-opus-4-8 or claude-sonnet-4-5 entries in this plugin's registry, so I cannot say whether they are affected.

AgentContext.systemPrompt is declared string in @earendil-works/pi-agent-core (dist/types.d.ts:347), so the assumption at line 371 is reasonable — but nothing enforces it at runtime (agent.js:30 uses initialState?.systemPrompt ?? "", which guards only nullish), so a non-string reaches this line intact. Filing here because this is where it throws; a fix may well be needed on the pi side instead of, or as well as, here.

I measured the runtime shape rather than guessing, by temporarily throwing a diagnostic immediately before line 371:

DIAG-SYSPROMPT typeof=object isArray=true ctor=Array fnName=n/a len=n/a keys=["0","1","2"]

So: an array of 3 elements. I did not capture the elements themselves — their type, keys, or whether any carry cache_control is unmeasured. I can instrument that on request.

A follow-on result, which is speculative — please read the caveat

To test whether a defensive coercion would be sufficient, I locally flattened an array systemPrompt into a single string before line 371 (joining any .text values). That removed the crash and a task subagent produced real model output. The next request then failed differently:

Anthropic request failed: HTTP 400
{"type":"error","error":{"type":"invalid_request_error",
"message":"A maximum of 4 blocks with cache_control may be provided. Found 5."}}

Caveats, stated plainly because this part is inference rather than measurement:

  • It was observed only with my local patch applied, never in stock 1.22.0 — in stock the crash happens first and would mask it.
  • I did not verify where those 5 cache_control blocks come from. They could originate in this plugin's own system[] composition, in later message composition, or in the incoming array. I never captured the final request body.
  • I therefore cannot claim the 3 incoming elements carry cache_control. That was my working hypothesis, not a finding.

I mention it only because it suggests a plain string coercion at line 371 may not be sufficient on its own, and because you may recognise the block accounting immediately. I have reverted the patch and am not proposing a fix, since the right behaviour here is your design call.

To Reproduce

  1. Run Oh My Pi (omp 18.1.13) with @cortexkit/pi-anthropic-auth@1.22.0 installed as a plugin.
  2. Set the subagent model to one this plugin serves, e.g. modelRoles.task: anthropic/claude-sonnet-5.
  3. Spawn any subagent (task tool) with any instruction.
  4. It fails within ~1-2s with the error above. No request reaches Anthropic.

Expected behavior

A subagent spawn produces a valid request — either by normalising systemPrompt to text before .trim() / splitPiSystemPrompt(), or by handling a structured value explicitly.

What narrows it down

This plugin's model registry covers claude-opus-4-5, claude-opus-4-8, claude-opus-5, claude-sonnet-4-5, claude-sonnet-5 — no Haiku. Observed on my machine, same agent definition with only the model: line changed:

Subagent model Served by Result
claude-sonnet-5 this plugin crash
claude-opus-5 this plugin crash
claude-haiku-4-5 pi's own Anthropic provider works
openai-codex/gpt-5.6-sol codex provider works

Since only the model changed, the model appears to select which converter handles the request rather than causing the fault.

I have not measured what the working providers receive. For completeness, their code simply never calls .trim() on that field — openai-codex-responses.js:346 does context.systemPrompt || "You are a helpful assistant." and anthropic-messages.js:689 does if (context.systemPrompt) then sanitizeSurrogates(...) — but whether they are handed the same array, and if so what they do with it, is untested by me and is a question for the pi maintainers rather than a claim here.

Also worth noting: the main (non-subagent) session works fine on claude-opus-5 through this same plugin. Only the subagent spawn path produced the array in my testing.

Environment

  • OS: Windows 11 (Windows_NT 10.0.26200)
  • Harness: Oh My Pi omp/18.1.13 (not opencode)
  • Node: v22.16.0
  • @cortexkit/pi-anthropic-auth: 1.22.0 (confirmed latest on npm at time of filing)
  • @earendil-works/pi-agent-core: 0.80.6
  • @earendil-works/pi-coding-agent: 0.80.6

Additional context

  • I am on OMP rather than opencode, so the rm -rf ~/.cache/opencode step does not apply; I did confirm 1.22.0 is the current published version and searched open issues for systemPrompt and trim before filing (no matches).
  • My workaround is to pin affected agents to a model outside this plugin (model: "@review"openai-codex), which restores subagents without touching the plugin.
  • All local instrumentation and patches are reverted; the installed convert.js is byte-identical to the published file.
  • Happy to run further instrumentation — in particular dumping each array element's type/keys/cache_control and the final request's cache_control locations, which would settle the follow-on question above. Just say what you would like captured.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions