You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Version:dev @ 8c6d4dc; same behavior on v7.3.4 Path:/v1/responses → interactions → Devin OAuth (devin/swe-2) Client:pi coding agent (Responses API); also reproducible with curl OS: CPA in Docker on Ubuntu/arm64
Summary
The OpenAI Responses API accepts the system prompt in two places: top-level instructions, or input items with role: "system" / "developer" (with or without an explicit "type": "message"). The interactions request translator only handles instructions. Any system/developer item inside input is converted to a user_input step, so:
the interactions payload has no system_instruction,
the Devin wire request has no system prompt (field 2 is omitted),
the prompt text is delivered as the first user message, indistinguishable from the user's own turns.
The sibling Responses translators do this correctly: the Gemini one normalizes a missing type to message and routes system/developer into systemInstruction (that was #2791), and the Devin executor's fallback for raw OpenAI messages also recognizes system/developer. Only the interactions path is missing both steps.
Evidence
Offline run of the real code path (ConvertOpenAIResponsesRequestToInteractions → parseInteractionsPayload) on dev @ 8c6d4dc, system prompt S, user turn hi:
That 53 KB agent prompt reaches Devin as the first of ~200 user-role prompts rather than as the system prompt. (The table above is from running the translator and parseInteractionsPayload directly on that shape; SystemPrompt is json:"system_prompt,omitempty", so an empty value drops the key from the upstream request entirely.)
Cause — internal/translator/openai/interactions/responses/interactions_openai_responses_request.go
ConvertOpenAIResponsesRequestToInteractions: only root.Get("instructions") feeds system_instruction
responsesInputItemToInteractions, case "message": role is only checked for assistant/model; everything else becomes user_input
no type == "" && role != "" → message normalization, so shorthand items fall to the default branch and become user_input regardless of role (a shorthand assistant item is also mis-typed as user)
Behavior note: because the text is still delivered (as a user turn), short instructions are usually still followed; in controlled tests a one-line instruction and a 53 KB prompt with embedded markers were honored either way. The defect is that the system/user distinction is lost on the wire, which matters for long agent prompts buried in long histories and for anything upstream that treats the system prompt differently from user turns.
Related: SanitizeDevinSystemPrompt drops lines by generic substring
internal/runtime/executor/helps/devin_wire.goSanitizeDevinSystemPrompt removes whole lines that contain authorized security testing or destructive techniques, DoS attacks (alongside the Claude-Code-specific filters such as You are Claude Code, Claude Code is available as a CLI). Those two patterns are generic wording, so a non-Claude-Code harness whose policy text uses the same phrases silently loses the line. Example (no sensitive-words configured):
IN (5 lines) OUT (3 lines)
You are DeepSeek Harness. You are DeepSeek Harness.
Assist with defensive security tasks only; … authorized (dropped)
security testing …
Always read a file before editing it. Always read a file before editing it.
Claude Code is available as a CLI tool. (dropped — expected, Claude-specific)
Finish by running the test suite. Finish by running the test suite.
The client has no way to know a line was removed.
Steps to reproduce
curl https://<cpa>/v1/responses -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{ "model":"devin/swe-2","stream":false, "input":[{"role":"system","content":"MARKER-123: you are a terse assistant."}, {"role":"user","content":"Reply with exactly: OK"}]}'
With request-log: true, open the resulting logs/v1-responses-*.log and compare two sections:
=== INTERMEDIATE INTERACTIONS === — no system_instruction
=== DEVIN UPSTREAM REQUEST === — no system_prompt key (it is omitempty and the value is empty); prompts[0] carries the marker text with "role": "user"
Sending the same text as top-level instructions instead produces system_instruction and system_prompt as expected, with prompts holding only the real user turn.
Expected behavior
Leading system/developer items in input (typed or shorthand) are treated as the system prompt and reach Devin's system prompt field, merged with top-level instructions when both are present — consistent with the Gemini Responses translator after 400 invalid_argument on /v1/responses when translate developer-role input into gemini request #2791 and with the messages fallback in the Devin executor.
Shorthand items without type are treated as messages, so role: "assistant" maps to model_output.
The system prompt sanitizer only applies its Claude-Code-specific line filters to prompts that are identifiably Claude Code's; other clients' policy lines are forwarded unchanged.
Version:
dev@8c6d4dc; same behavior on v7.3.4Path:
/v1/responses→ interactions → Devin OAuth (devin/swe-2)Client:
picoding agent (Responses API); also reproducible with curlOS: CPA in Docker on Ubuntu/arm64
Summary
The OpenAI Responses API accepts the system prompt in two places: top-level
instructions, orinputitems withrole: "system"/"developer"(with or without an explicit"type": "message"). The interactions request translator only handlesinstructions. Any system/developer item insideinputis converted to auser_inputstep, so:system_instruction,The sibling Responses translators do this correctly: the Gemini one normalizes a missing
typetomessageand routes system/developer intosystemInstruction(that was #2791), and the Devin executor's fallback for raw OpenAImessagesalso recognizessystem/developer. Only the interactions path is missing both steps.Evidence
Offline run of the real code path (
ConvertOpenAIResponsesRequestToInteractions→parseInteractionsPayload) ondev@8c6d4dc, system promptS, user turnhi:system_instructioninstructions: SSS[user: hi]input: [{type:"message", role:"system", content:S}, …][user: S, user: hi]input: [{role:"system", content:S}, …](shorthand)[user: S, user: hi]input: [{role:"developer", content:S}, …][user: S, user: hi]Captured production request from a real client (
User-Agent: pi (linux …)), structure only:That 53 KB agent prompt reaches Devin as the first of ~200 user-role prompts rather than as the system prompt. (The table above is from running the translator and
parseInteractionsPayloaddirectly on that shape;SystemPromptisjson:"system_prompt,omitempty", so an empty value drops the key from the upstream request entirely.)Cause —
internal/translator/openai/interactions/responses/interactions_openai_responses_request.goConvertOpenAIResponsesRequestToInteractions: onlyroot.Get("instructions")feedssystem_instructionresponsesInputItemToInteractions,case "message": role is only checked forassistant/model; everything else becomesuser_inputtype == "" && role != ""→messagenormalization, so shorthand items fall to thedefaultbranch and becomeuser_inputregardless of role (a shorthandassistantitem is also mis-typed as user)Behavior note: because the text is still delivered (as a user turn), short instructions are usually still followed; in controlled tests a one-line instruction and a 53 KB prompt with embedded markers were honored either way. The defect is that the system/user distinction is lost on the wire, which matters for long agent prompts buried in long histories and for anything upstream that treats the system prompt differently from user turns.
Related:
SanitizeDevinSystemPromptdrops lines by generic substringinternal/runtime/executor/helps/devin_wire.goSanitizeDevinSystemPromptremoves whole lines that containauthorized security testingordestructive techniques, DoS attacks(alongside the Claude-Code-specific filters such asYou are Claude Code,Claude Code is available as a CLI). Those two patterns are generic wording, so a non-Claude-Code harness whose policy text uses the same phrases silently loses the line. Example (nosensitive-wordsconfigured):The client has no way to know a line was removed.
Steps to reproduce
With
request-log: true, open the resultinglogs/v1-responses-*.logand compare two sections:=== INTERMEDIATE INTERACTIONS ===— nosystem_instruction=== DEVIN UPSTREAM REQUEST ===— nosystem_promptkey (it isomitemptyand the value is empty);prompts[0]carries the marker text with"role": "user"Sending the same text as top-level
instructionsinstead producessystem_instructionandsystem_promptas expected, withpromptsholding only the real user turn.Expected behavior
system/developeritems ininput(typed or shorthand) are treated as the system prompt and reach Devin's system prompt field, merged with top-levelinstructionswhen both are present — consistent with the Gemini Responses translator after 400 invalid_argument on /v1/responses when translate developer-role input into gemini request #2791 and with themessagesfallback in the Devin executor.typeare treated as messages, sorole: "assistant"maps tomodel_output.