Skip to content

fix(opencode-go): send x-opencode-session header for session affinity - #81591

Open
chelsealong wants to merge 2 commits into
NousResearch:mainfrom
chelsealong:fix/opencode-go-session-affinity-header
Open

fix(opencode-go): send x-opencode-session header for session affinity#81591
chelsealong wants to merge 2 commits into
NousResearch:mainfrom
chelsealong:fix/opencode-go-session-affinity-header

Conversation

@chelsealong

@chelsealong chelsealong commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

OpenCode Go's chat_completions relay returns HTTP 400 Model is unavailable for
some backends (confirmed for deepseek-v4-flash) unless the request carries a
stable, opaque x-opencode-session header. Hermes' opencode-go provider profile
never sent that header, so the very first request to an affected model fails.

This adds x-opencode-session to OpenCodeGoProfile.build_api_kwargs_extras,
resolved with the same pattern already used for OpenRouter's x-grok-conv-id
sticky-routing header: the ambient conversation context first, falling back to
the explicit session_id, normalized through the existing
_cache_scope_from_session_id helper. That keeps the value:

  • stable across turns of the same conversation,
  • correctly rotated after session rotation/compression (it's derived from the
    session-lineage root, same as the OpenRouter/xAI header),
  • scoped to this provider only — it's added inside the opencode-go profile's
    own hook, so it can't leak into other providers' requests.

This does not send any official-OpenCode-CLI impersonation headers
(User-Agent: opencode-cli, x-opencode-client: cli) — only the honest,
non-impersonating session-affinity header, matching the approach the issue
explicitly asks for.

Related Issue

Fixes #81584
Fixes #81832

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • plugins/model-providers/opencode-zen/__init__.py: OpenCodeGoProfile.build_api_kwargs_extras
    now attaches extra_headers["x-opencode-session"] whenever a conversation/session
    id is available, on top of the existing reasoning-control logic (moved unchanged
    into a private _reasoning_api_kwargs_extras helper).
  • tests/plugins/model_providers/test_opencode_go_profile.py: added
    TestOpenCodeGoSessionAffinityHeader covering header presence with a
    session_id, cron-timestamp normalization/stability (mirrors the existing
    OpenRouter grok-header test), header omission with no session context, and
    coexistence with the existing reasoning top_level kwargs.

How to Test

  1. python -m pytest tests/plugins/model_providers/test_opencode_go_profile.py -q
  2. Reproduce the failure without the fix: git checkout HEAD~1 -- plugins/model-providers/opencode-zen/__init__.py,
    rerun the new tests (they fail with KeyError: 'extra_headers'), then
    git checkout HEAD -- plugins/model-providers/opencode-zen/__init__.py to restore the fix.

Actual output

Without the fix (source reverted, tests kept):

FAILED ...::TestOpenCodeGoSessionAffinityHeader::test_session_id_sets_affinity_header - KeyError: 'extra_headers'
FAILED ...::TestOpenCodeGoSessionAffinityHeader::test_header_normalizes_cron_timestamp - KeyError: 'extra_headers'
FAILED ...::TestOpenCodeGoSessionAffinityHeader::test_header_coexists_with_reasoning_top_level_kwargs - KeyError: 'extra_headers'
3 failed, 1 passed, 20 deselected in 1.02s

With the fix:

........................
24 passed in 2.07s

Also ran the broader related suites with no regressions:

tests/plugins/model_providers/test_opencode_go_profile.py
tests/providers/test_provider_profiles.py
tests/hermes_cli/test_opencode_go_validation_fallback.py
tests/hermes_cli/test_opencode_zen_model_limit.py
tests/hermes_cli/test_model_switch_opencode_anthropic.py
tests/hermes_cli/test_opencode_go_flat_namespace.py
tests/hermes_cli/test_opencode_go_in_model_list.py
tests/agent/transports/test_chat_completions.py
tests/agent/test_auxiliary_client.py
=> 51 + 45 + 172 passed, 0 failed

ruff check on both changed files: all checks passed.

Checklist

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(opencode-go):)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run pytest tests/ -q (relevant subset — see above) and all tests pass
  • I've added tests for my changes
  • Tested on Ubuntu 24.04

Documentation & Housekeeping

  • N/A — no user-facing config/docs changes

AI assistance disclosure

This PR was prepared with AI assistance (Claude, via an autonomous coding agent),
reviewed and tested before submission.

OpenCode Go's chat_completions relay 400s "Model is unavailable" for
some backends (e.g. deepseek-v4-flash) unless a stable per-conversation
session-affinity header is present. Add x-opencode-session, resolved the
same way OpenRouter's x-grok-conv-id already is (ambient conversation
context first, explicit session_id fallback), so the value stays stable
across turns, rotates with the conversation, and never leaks to other
providers.

Fixes NousResearch#81584
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins provider/deepseek DeepSeek API labels Aug 8, 2026
@Enough1122

Copy link
Copy Markdown
Contributor

Thanks for this fix! I applied it locally (cherry-picked both files into the provider profile) and can confirm it resolves the issue end-to-end: deepseek-v4-flash via opencode-go now works reliably, where previously every chat/completions request returned HTTP 400 "Model is unavailable" without the x-opencode-session header. Verified with the new session-affinity tests plus the broader opencode-go suites (24 + 51 passed, no regressions), and in live desktop usage. Looking forward to seeing this merged into main!

@spfcraze

spfcraze commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Summary:
Fixes #81584 will auto-close the issue on merge, but the header is attached only where build_api_kwargs_extras runs — the anthropic_messages and codex_responses transports never call it, and the issue's regression coverage item 1 names all three routes.

Problems:

  • build_api_kwargs_extras is invoked only from agent/transports/chat_completions.py:670 and agent/auxiliary_client.py:8031; neither agent/transports/anthropic.py nor agent/transports/codex.py calls it, so opencode-go turns on the anthropic_messages or codex_responses routes still send no x-opencode-session header after this change.
  • The profile's per-model routing sends GPT models to codex_responses and MiniMax/Qwen to anthropic_messages (docstring in plugins/model-providers/opencode-zen/__init__.py), so opencode-go serves those routes.
  • Issue [Bug]: OpenCode Go deepseek-v4-flash fails with 400 "Model is unavailable" unless x-opencode-session is sent #81584's regression coverage item 1 asks for the header on chat_completions, anthropic_messages, and responses routes.

Solution:
Extend the header to the anthropic_messages and codex_responses transport paths as well.


Checked against 8fed6e0 — the tip of fix/opencode-go-session-affinity-header when this was written — and 2389564, main at the same moment.

…_responses too

build_api_kwargs_extras (where the header was added) is only consulted
by the chat_completions transport, so MiniMax/Qwen models on OpenCode Go
- which route through anthropic_messages - and any future codex_responses
routing still went out with no session-affinity header. Merge it in
build_api_kwargs for both routes the same way Nous Portal's tags/session_id
are merged onto the Messages wire.
@chelsealong

Copy link
Copy Markdown
Contributor Author

Good catch — pushed 26654cc: build_api_kwargs in agent/chat_completion_helpers.py now merges the x-opencode-session header onto the anthropic_messages route (MiniMax/Qwen on OpenCode Go) and the codex_responses route, the same way Nous Portal's tags/session_id are merged onto the Messages wire — build_api_kwargs_extras is only consulted by the chat_completions transport otherwise. Added TestOpenCodeGoSessionHeaderOtherTransports covering both routes; confirmed the new tests fail (KeyError: 'extra_headers') without the fix and pass with it. Ran the full previously-listed suite plus these + codex/anthropic transport tests (368 passed) and ruff check on both changed files.

@alt-glitch alt-glitch added comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 8, 2026
@DavidMetcalfe

Copy link
Copy Markdown
Contributor

This PR also fixes the failure reported in #81832 (same root cause: opencode-go + deepseek-v4-flash, HTTP 400 without x-opencode-session; different upstream error message text but the same missing-header trigger). Consider adding "Fixes #81832" to the PR body so both issues auto-close on merge.

@alt-glitch alt-glitch added provider/minimax MiniMax (Anthropic transport) provider/qwen Qwen / Alibaba Cloud (OAuth) labels Aug 8, 2026
@chelsealong

Copy link
Copy Markdown
Contributor Author

Added "Fixes #81832" to the PR body per your note — same root cause, so it'll auto-close alongside #81584 on merge. No code changes needed.

igangz pushed a commit to igangz/hermes-agent that referenced this pull request Aug 10, 2026
…esearch#25437)

Followup to PR NousResearch#24182 — caught when scanning OpenClaw for recent codex
fixes we hadn't considered. OpenClaw learned the hard way (NousResearch#80815) that
migrating plugins which codex itself reports as unavailable produces
config that fails at activation time.

Our /codex-runtime codex_app_server enable path queries codex's
plugin/list and migrates everything where installed=true. We were
trusting codex's installation state and ignoring its availability
field. So a plugin that's installed=true but availability=UNAVAILABLE
(broken local install) or REQUIRES_AUTH (OAuth expired or never
completed) would get an [plugins."<n>@openai-curated"] entry in
~/.codex/config.toml — and the user's first codex turn after enabling
the runtime would fail because codex refuses to activate it.

Fix: filter on availability in _query_codex_plugins(). Only emit
plugins where availability is empty (older codex versions without the
field — preserve backward compat) or explicitly AVAILABLE.

Tests:
  test_plugin_discovery_skips_unavailable_plugins — verifies 4 cases:
    - good-plugin (installed=True, availability=AVAILABLE) → migrated
    - broken-plugin (installed=True, availability=UNAVAILABLE) → skipped
    - auth-pending (installed=True, availability=REQUIRES_AUTH) → skipped
    - legacy-plugin (installed=True, no availability field) → migrated
      (older codex versions; preserve backward compat)

Docs:
  Added bullet to 'What's NOT migrated' list in the docs page calling
  out the availability filter and why.

Other OpenClaw codex PRs I reviewed but did NOT apply (with reasoning):
  - NousResearch#81591 (load Codex for selectable models): we resolve runtime
    per-call already, no startup-time gating to fix
  - NousResearch#81510 (cron compatibility): we documented cron as untested; their
    fix is for OpenClaw-specific cron orchestration shape
  - NousResearch#81223 (rotate incompatible context-engine threads): we don't
    have a Lossless context engine equivalent
  - NousResearch#80688 (constrain sandbox): we don't have an outer-sandbox concept
  - NousResearch#80616 (release on turn_aborted): we already handle status=
    interrupted in turn/completed correctly
  - NousResearch#80278 (expose activeModel in plugin SDK): not our surface
  - NousResearch#80792 (default destructive_actions on): we don't expose that knob

56 codex-runtime migration tests still green (+1 new).
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…esearch#25437)

Followup to PR NousResearch#24182 — caught when scanning OpenClaw for recent codex
fixes we hadn't considered. OpenClaw learned the hard way (NousResearch#80815) that
migrating plugins which codex itself reports as unavailable produces
config that fails at activation time.

Our /codex-runtime codex_app_server enable path queries codex's
plugin/list and migrates everything where installed=true. We were
trusting codex's installation state and ignoring its availability
field. So a plugin that's installed=true but availability=UNAVAILABLE
(broken local install) or REQUIRES_AUTH (OAuth expired or never
completed) would get an [plugins."<n>@openai-curated"] entry in
~/.codex/config.toml — and the user's first codex turn after enabling
the runtime would fail because codex refuses to activate it.

Fix: filter on availability in _query_codex_plugins(). Only emit
plugins where availability is empty (older codex versions without the
field — preserve backward compat) or explicitly AVAILABLE.

Tests:
  test_plugin_discovery_skips_unavailable_plugins — verifies 4 cases:
    - good-plugin (installed=True, availability=AVAILABLE) → migrated
    - broken-plugin (installed=True, availability=UNAVAILABLE) → skipped
    - auth-pending (installed=True, availability=REQUIRES_AUTH) → skipped
    - legacy-plugin (installed=True, no availability field) → migrated
      (older codex versions; preserve backward compat)

Docs:
  Added bullet to 'What's NOT migrated' list in the docs page calling
  out the availability filter and why.

Other OpenClaw codex PRs I reviewed but did NOT apply (with reasoning):
  - NousResearch#81591 (load Codex for selectable models): we resolve runtime
    per-call already, no startup-time gating to fix
  - NousResearch#81510 (cron compatibility): we documented cron as untested; their
    fix is for OpenClaw-specific cron orchestration shape
  - NousResearch#81223 (rotate incompatible context-engine threads): we don't
    have a Lossless context engine equivalent
  - NousResearch#80688 (constrain sandbox): we don't have an outer-sandbox concept
  - NousResearch#80616 (release on turn_aborted): we already handle status=
    interrupted in turn/completed correctly
  - NousResearch#80278 (expose activeModel in plugin SDK): not our surface
  - NousResearch#80792 (default destructive_actions on): we don't expose that knob

56 codex-runtime migration tests still green (+1 new).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have provider/deepseek DeepSeek API provider/minimax MiniMax (Anthropic transport) provider/qwen Qwen / Alibaba Cloud (OAuth) sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

5 participants