Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[Provider compatibility] ollama-native rejects Codex exec histories with more than one output per call, breaking routed compaction

Closed
#6,574 1 comment 0 reactions 0 assignees View on GitHub

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

provider provider-compatibility
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 each notify() call adds another output for the same call_id.
  • A still-running cell can record its notify() output after later items, for example after a wait function call and its function_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 toolResult for 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 toolResult whose 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from lidge-jun/opencodex

All issues in lidge-jun/opencodex

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.