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
- Run Oh My Pi (
omp 18.1.13) with @cortexkit/pi-anthropic-auth@1.22.0 installed as a plugin.
- Set the subagent model to one this plugin serves, e.g.
modelRoles.task: anthropic/claude-sonnet-5.
- Spawn any subagent (
task tool) with any instruction.
- 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.
Describe the bug
convertToAnthropicindist/convert.js:371callscontext.systemPrompt?.trim(). When the caller suppliessystemPromptas an array rather than a string,?.does not short-circuit (the value is not nullish) and the call throws:Under Oh My Pi, every subagent spawn I tried failed at request time before any HTTP call. Measured on
claude-opus-5andclaude-sonnet-5only — I did not test theclaude-opus-4-5,claude-opus-4-8orclaude-sonnet-4-5entries in this plugin's registry, so I cannot say whether they are affected.AgentContext.systemPromptis declaredstringin@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:30usesinitialState?.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:
So: an array of 3 elements. I did not capture the elements themselves — their
type, keys, or whether any carrycache_controlis 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
systemPromptinto a single string before line 371 (joining any.textvalues). That removed the crash and atasksubagent produced real model output. The next request then failed differently:Caveats, stated plainly because this part is inference rather than measurement:
cache_controlblocks come from. They could originate in this plugin's ownsystem[]composition, in later message composition, or in the incoming array. I never captured the final request body.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
omp18.1.13) with@cortexkit/pi-anthropic-auth@1.22.0installed as a plugin.modelRoles.task: anthropic/claude-sonnet-5.tasktool) with any instruction.Expected behavior
A subagent spawn produces a valid request — either by normalising
systemPromptto 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 themodel:line changed:claude-sonnet-5claude-opus-5claude-haiku-4-5openai-codex/gpt-5.6-solSince 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:346doescontext.systemPrompt || "You are a helpful assistant."andanthropic-messages.js:689doesif (context.systemPrompt)thensanitizeSurrogates(...)— 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-5through this same plugin. Only the subagent spawn path produced the array in my testing.Environment
Windows_NT 10.0.26200)omp/18.1.13(not opencode)@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.6Additional context
rm -rf ~/.cache/opencodestep does not apply; I did confirm 1.22.0 is the current published version and searched open issues forsystemPromptandtrimbefore filing (no matches).model: "@review"→openai-codex), which restores subagents without touching the plugin.convert.jsis byte-identical to the published file.type/keys/cache_controland the final request'scache_controllocations, which would settle the follow-on question above. Just say what you would like captured.