Skip to content

Raw citation control markers such as citeturn1view0 leak into Codex TUI output #3150

Description

@mushikingh

Summary

When Codex CLI is routed through OpenCodex to the GitHub Copilot provider, assistant messages containing web citations can display OpenAI/ChatGPT-style citation control markers literally:

The setting is supported. citeturn1view0turn1view1

The same raw markers are retained in the Codex transcript. They are not rendered as source links, converted to a readable citation, or removed.

The delimiters are Unicode private-use characters. The example above is:

\uE200cite\uE202turn1view0\uE202turn1view1\uE201
  • U+E200 begins the citation marker.
  • U+E202 separates cite and each source reference.
  • U+E201 ends the marker.
  • Values such as turn1view0 are opaque, turn-scoped source identifiers and are not useful when displayed directly to the user.

This appears to be a response-compatibility issue: citation presentation syntax reaches the TUI as ordinary assistant text, but the TUI does not understand or render it.

Environment

Observed on September 1, 2026 with:

  • OpenCodex package: @bitkyc08/opencodex@2.10.1-preview.20260805
  • Installed package artifact: 2.10.1-preview.20260805-4f47f002f0b85fef
  • Codex CLI: 0.151.0
  • Provider: github-copilot
  • Model route: github-copilot/gpt-5.6-sol
  • OS: macOS 26.6.2, Apple Silicon (arm64)
  • Terminal: TERM=xterm-256color, COLORTERM=truecolor
  • API mode: streaming OpenAI Responses API through the local OpenCodex proxy

Minimal relevant Codex configuration:

model = "github-copilot/gpt-5.6-sol"
openai_base_url = "http://127.0.0.1:10100/v1"

Reproduction

  1. Start OpenCodex:

    ocx start --port 10100
  2. Configure Codex CLI to use the local OpenCodex Responses endpoint and a GitHub Copilot-backed model:

    model = "github-copilot/gpt-5.6-sol"
    openai_base_url = "http://127.0.0.1:10100/v1"
  3. Start Codex CLI.

  4. Ask a question that causes a web lookup and requires source citations. For example:

    Verify the current Codex configuration value for automatic approval review using official documentation.
    
  5. Observe a citation-bearing commentary or final assistant message.

  6. Open the Codex transcript with Ctrl+T.

Actual behavior

The TUI and transcript contain literal private-use citation syntax:

The valid value is auto_review. citeturn1view0turn1view1

Consequences:

  • The user sees unusual glyphs and internal source IDs.
  • The references are not clickable or human-readable.
  • Copying the answer also copies the protocol/presentation markers.
  • The persisted transcript contains client-specific rendering artifacts.
  • Multiple source IDs make the leaked text especially noisy.

The issue occurs in both intermediate commentary and the final answer, so it does not appear limited to one Codex message phase.

Expected behavior

Raw citation control markers should never be visible in user-facing assistant text.

Depending on available source metadata and client capability, OpenCodex should do one of the following:

  1. Preserve/emit structured url_citation annotations that the client can render.
  2. Convert citations to a readable TUI fallback, such as Markdown links or a short Sources list.
  3. If the opaque reference cannot be resolved, remove the presentation marker cleanly rather than exposing private-use delimiters and internal IDs.

The desktop citation/Sources-chip behavior should remain intact.

Relevant OpenCodex implementation details

The installed source already has structured web-source support:

  • src/types.ts

    • OcxUrlCitation represents a web source.
    • Its comment explicitly notes that the desktop app reads url_citation annotations while the TUI ignores annotations.
  • src/web-search/loop.ts

    • Collects and deduplicates web-search sources.
    • Sends them through web_search_call_end.
  • src/bridge.ts

    • Accumulates pendingWebSources.
    • takeWebAnnotations() converts them into url_citation annotations.
    • closeCurrentMessage() emits response.output_text.done, response.content_part.done, and response.output_item.done.
    • The non-streaming flushText() path also attaches annotations.
  • src/web-search/parse.ts

    • Parses structured url_citation annotations.
    • Also extracts a trailing Markdown Sources: block.

I could not find handling for the private-use marker grammar (U+E200, U+E202, and U+E201) in the installed OpenCodex source.

The observed turnNviewN references may originate from a Codex-hosted web tool rather than OpenCodex's synthetic web-search sidecar. Therefore, the first diagnostic step should be to determine whether the markers are:

  1. Already present as literal text in the upstream GitHub Copilot response.
  2. Produced while OpenCodex translates an upstream response into Responses API events.
  3. Present only after Codex CLI processes an otherwise structured downstream response.

This report does not assume which component originally creates the marker. The actionable compatibility problem is that the OpenCodex-routed path allows it to reach user-visible TUI text without a usable fallback.

Suggested investigation

Capture one sanitized failing streaming turn at both sides of the proxy:

  1. The upstream provider's assistant text/events.
  2. OpenCodex's downstream Responses API SSE events.

Inspect these downstream event fields in particular:

response.output_text.delta
response.output_text.done
response.content_part.done
response.output_item.done
response.completed

Questions to answer:

  • Do the private-use characters first appear upstream or downstream?
  • Are url_citation annotations present at the same time as literal markers?
  • Does the final completed response differ from the streamed deltas?
  • Does the behavior reproduce in non-streaming mode?
  • Does it reproduce with another OpenCodex provider?
  • Does the same request render correctly when Codex uses an official provider directly?
  • Does the Codex desktop app render the response correctly while the TUI leaks the marker?

Possible fix direction

If OpenCodex receives these markers as literal provider text, add a narrowly scoped citation normalizer at the assistant-output bridge boundary.

Important implementation constraints:

  • The parser must be streaming-safe. A marker can be split across arbitrary response.output_text.delta boundaries.
  • Normalize both streaming deltas and authoritative final text so .delta, .done, content-part, output-item, and completed snapshots remain consistent.
  • Match only the exact citation grammar; do not remove arbitrary Unicode private-use characters globally.
  • Apply normalization only to assistant output, never user messages or tool-result payloads.
  • Avoid emitting both a normalized textual fallback and duplicate structured annotations.
  • Preserve existing desktop url_citation/Sources-chip behavior.
  • If source IDs can be mapped to URLs from the associated tool result, prefer proper annotations or readable Markdown links.
  • If no mapping exists, fail closed by removing the control syntax and opaque IDs from display rather than leaking them.

A stateful stream normalizer may need to retain a small suffix beginning at U+E200 until it sees U+E201 or can determine that the sequence is not a valid citation marker.

Suggested tests

Add unit/integration coverage for both streaming and non-streaming response paths:

  1. One citation with one source:

    before \uE200cite\uE202turn1view0\uE201 after
    
  2. One citation with multiple sources.

  3. Multiple citations in one assistant message.

  4. A citation adjacent to punctuation and at end-of-message.

  5. Every possible application-level split between Unicode scalar values within the marker. If normalization happens below SSE decoding, also test transport-byte splits within a multibyte UTF-8 delimiter.

  6. A marker split across the final delta and .done event.

  7. Existing structured url_citation annotations without marker text.

  8. Marker text plus structured annotations, ensuring sources are not duplicated.

  9. A malformed/unclosed marker, ensuring output is deterministic and no stream data is lost.

  10. Ordinary Unicode and unrelated private-use characters, ensuring they remain unchanged.

  11. Commentary-phase and final-answer messages.

  12. Transcript/snapshot output, ensuring raw markers are not persisted.

Acceptance criteria

  • No literal U+E200 cite U+E202 ... U+E201 sequence is displayed in Codex TUI output.
  • No raw citation sequence is retained in the transcript.
  • Citation-bearing responses remain readable when the TUI cannot render annotations.
  • Resolvable sources remain attributable through annotations, Markdown links, or a readable source list.
  • Streaming and non-streaming outputs are equivalent.
  • Citation markers split across SSE chunks are handled correctly.
  • Existing desktop Sources-chip behavior is not regressed.
  • User/tool content containing similar text is not modified.

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

    bugSomething isn't workingneeds-infoWaiting on reporter for a concrete spec or reproductionproviderProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reports

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions