[Provider compatibility] ollama-native rejects Codex exec histories with more than one output per call, breaking routed compaction
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 75/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- ollama, typescript
- Domain
- api
Research direction
Start with buildNativeMessages() in the ollama-native adapter and compare how the anthropic and google adapters handle extra or late tool results. Run the provided offline reproduction and review the three existing tests named in the issue that encode the current refusal. Done means previously issued calls with additional or late outputs no longer cause the request to fail, while results for unknown call IDs can remain errors.
Written by the indexing model from the issue text.
Description
Client or integration
Codex App
Provider or upstream service
ollama-cloud (provider adapter: "ollama-native"), observed with ollama-cloud/deepseek-v4.1-flash. The failing function serves every routed Ollama model.
OpenCodex version
Observed on 2.75.0. Reproduced offline on dev 33185c2cc (2.78.0).
Endpoint or capability
POST /v1/responses: remote compaction turns routed by compactionRouting, and any ordinary routed turn that replays Codex code-mode exec history.
Current behaviour
One Codex code-mode exec call can leave more than one custom_tool_call_output with the same call_id in the history:
- When the cell outlives
yield_time_ms, Codex records the yield marker (Script running with cell ID N ...), and eachnotify()call adds another output for the samecall_id. - A still-running cell can record its
notify()output after later items, for example after awaitfunction call and itsfunction_call_output.
buildNativeMessages() rejects both shapes before anything is sent upstream (400, failureStage: headers-only):
- adjacent outputs:
ollama-native duplicate tool result for <id> - a late output after another call's batch:
ollama-native tool result <id> has no originating call
The damage is largest with compactionRouting. When compaction for every session goes to an Ollama Cloud model, a session whose history contains such an exec call can no longer compact. Codex reports Error running remote compact task, and a retry fails the same way because the history does not change. On one installation, 3 of 49 routed compaction attempts over two days failed like this. The other 46 succeeded, with inputs of 340k to 590k tokens.
The anthropic, google and openai-chat adapters build a request from the same parsed history. Anthropic and Google pair the first result with its call and carry the extra outputs as explicit orphan-result text.
Expected behaviour
ollama-native should not fail a request just because Codex recorded more than one output for one exec call. Two acceptable behaviours:
- Merge additional outputs for a call into its result, as #3750 did for Kiro in the adjacent case.
- Keep the first result paired and carry extra or late outputs as explicit user-side text, which is what the anthropic and google adapters already do.
A result whose call id never appeared in the history can stay an error if that is the intended policy. A late or additional output for a call that did appear earlier should not fail the request.
Minimal redacted request or reproduction
Run from the repository root on dev (no network, no credentials):
import { parseRequest } from "./src/responses/parser";
import { createOllamaNativeAdapter } from "./src/adapters/ollama-native";
import { createAnthropicAdapter } from "./src/adapters/anthropic";
const user = (text: string) => ({ type: "message", role: "user", content: [{ type: "input_text", text }] });
const execCall = (id: string) => ({ type: "custom_tool_call", call_id: id, name: "exec", input: "notify('progress')" });
const execOut = (id: string, output: string) => ({ type: "custom_tool_call_output", call_id: id, output });
const waitCall = (id: string) => ({ type: "function_call", call_id: id, name: "wait", arguments: JSON.stringify({ cell_id: "31" }) });
const waitOut = (id: string, output: string) => ({ type: "function_call_output", call_id: id, output });
const tools = [
{ type: "custom", name: "exec", description: "Run JavaScript" },
{ type: "function", name: "wait", description: "Wait on a yielded exec cell",
parameters: { type: "object", properties: { cell_id: { type: "string" } }, required: ["cell_id"] } },
];
const yieldMarker = "Script running with cell ID 31\nWall time 31.0 seconds\nOutput:\n";
const shapes: Record<string, unknown[]> = {
control: [user("start"), execCall("call_A"), execOut("call_A", "done"), user("continue")],
adjacent: [user("start"), execCall("call_A"), execOut("call_A", yieldMarker), execOut("call_A", "progress 1"), user("continue")],
late: [user("start"), execCall("call_A"), execOut("call_A", yieldMarker),
waitCall("call_B"), waitOut("call_B", "Script completed\nOutput:\n"), execOut("call_A", "progress 2"), user("continue")],
};
const base = { authMode: "key", apiKey: "test-key", liveModels: false, models: ["deepseek-v4.1-flash"] } as any;
for (const [name, input] of Object.entries(shapes)) {
const parsed = parseRequest({ model: "deepseek-v4.1-flash", stream: true, input, tools });
for (const [label, adapter] of [
["ollama-native", createOllamaNativeAdapter({ ...base, adapter: "ollama-native", baseUrl: "https://ollama.com/v1" })],
["anthropic", createAnthropicAdapter({ ...base, adapter: "anthropic", baseUrl: "https://api.anthropic.com" })],
] as const) {
try { await adapter.buildRequest(structuredClone(parsed)); console.log(name, label, "ok"); }
catch (e: any) { console.log(name, label, "THROWS", e.message); }
}
}
Output on 33185c2cc. The google and openai-chat adapters also return ok for every shape.
control ollama-native ok
control anthropic ok
adjacent ollama-native THROWS ollama-native duplicate tool result for call_A
adjacent anthropic ok
late ollama-native THROWS ollama-native tool result call_A has no originating call
late anthropic ok
The real history that failed had both shapes in one call: yield marker, notify() output, a wait call and result, then another notify() output for the first call.
Actual response or error
Error running remote compact task: {"error":{"message":"ollama-native duplicate tool result for toolu_<redacted>","type":"invalid_request_error","code":"invalid_request_error"}}
Usage log for the routed attempt: status 400, adapter ollama-native, failureStage headers-only, failureCause payload-rejected, sendCount 0.
Upstream documentation
No public Ollama specification covers more than one tool message per tool_call_id. The concrete client requirement is the Codex code-mode exec output sequence (notify() and yielded cells), the same requirement as #3734.
Suggested mapping or implementation notes
In buildNativeMessages():
- A
toolResultfor a call in the open batch that already has a result: append its content to that result in input order and keep error status if any part is an error (the #3750 rule). - A
toolResultwhose call id was issued in an earlier, already settled batch: emit it as explicit user-side text with a marker, as the anthropic adapter does ([tool_result without adjacent tool_use: ...]), instead of throwing. - Keep the error for ids that never appeared in the history, if that policy should stay.
The existing tests a duplicate result for the same call is still refused, duplicate results remain rejected while commentary holds an unresolved batch and a result cannot cross a subsequent tool-call batch encode the current refusal, so this is a deliberate policy change for those cases.
Additional context and attachments
- #3731 / #3734: the same Codex shape on Kiro. #3750 fixed adjacent outputs there; a late output after another call's result is still rejected by that rule.
- #4842: an ollama-native replay-ordering fix for injected developer messages.
- Workaround in use: avoid
notify()in code-mode cells, or route compaction to a provider whose adapter accepts this history.
Checks
- I searched existing provider and compatibility issues.
- The request and response were redacted.
- The expected behaviour is based on an upstream specification or a concrete client requirement.
- Dominant language
- TypeScript
- Stars
- 16.9k
- Forks
- 1.3k
- Avg merge
- 7h 46m
- Merged PRs (30d)
- 638
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from lidge-jun/opencodex
-
[Bug]: Business Premium seat plan self_serve_business_prolite is counted at baseline capacity weight 1 instead of 5Possibly taken A pull request linked to this issue is open or already merged. Openaccount-pool bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
lidge-jun/opencodex#7440 · 2 comments ·
Maintainers usually reply within 1 day
-
[Docs]: translate the #7395 OpenGateway and Mistral provider-guide sectionsPossibly taken @MannXo claimed this 2 days ago. Opendocumentation
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
lidge-jun/opencodex#7405 · 3 comments ·
Maintainers usually reply within 1 day
-
[Bug]: resolveCodexRuntime test depends on the host's installed CodexPossibly taken A pull request linked to this issue is open or already merged. Openbug cli
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
lidge-jun/opencodex#7403 · 2 comments ·
Maintainers usually reply within 1 day
-
account-pool enhancement needs-design proxy
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
lidge-jun/opencodex#6737 · 1 comment ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 5/5 Over a week Newbie friendliness 45/100
Maintainers usually reply within 1 day
All issues in lidge-jun/opencodex
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
maniator/verticopolis#1303 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 73/100
components-web-app/docs#297 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
appwrite/mcp-for-docs#23 · 1 comment ·
-
`@vanilla-extract/turbopack-plugin`: "getImageSize is not a function" with Next.js 16.4.x when a `.css.ts` file imports an imagePossibly taken @aevarOrigo claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
vanilla-extract-css/vanilla-extract#1799 · 1 comment ·
-
native
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
prime-radiant-inc/evener#4839 ·
Maintainers usually reply within 1 day