Skip to content

fix(reasoning): re-send without the id when the upstream cannot find a stripped reasoning item - #252

Merged
HTExplicit merged 3 commits into
mainfrom
fix/stripped-reasoning-item-not-found
Oct 7, 2026
Merged

HTExplicit merged 3 commits into
mainfrom
fix/stripped-reasoning-item-not-found

Conversation

@HTExplicit

@HTExplicit HTExplicit commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Reasoning recovery strips rejected ciphertext but keeps the reasoning item and its ID. Some upstreams then treat that ID as a reference to a stored item and reject the request with Item with id '…' not found. This change repairs that specific response once on the same account by removing IDs only from reasoning items whose ciphertext this forwarding attempt actually stripped. Items, summaries and other fields remain intact.

  • Require the named item to match the body actually sent; retain IDs when the item has no summary. Do not repair HTTP 401/402/403/407/429 or stream failures.
  • Reasoning recovery adds at most two sends per forwarding attempt: one ciphertext strip and one ID repair. Other compatibility resends, compact model fallback and Agent Identity recovery are counted separately. Handler retries begin again from the original request.
  • Keep ID repair state local to this attempt. No new cross-request memory is written. Native Responses, passthrough and Responses-shaped Chat Completions requests use the same repair.
  • Prevent a prepared but unsent repair after the stripped retry from triggering account mismatch or account failure handoff. Record the repair event with its own response status and request ID, and report an unsent repair's reason while preserving whether the earlier stripped retry was sent. A repair after cached pre-stripping retains the builder's original error contract.
  • Apply field deletions in one pass over the body and preserve the existing projection and fallback checks.

Validation on the final patch uses unit-tagged service tests: the previously interrupted TestOpenAIReasoningRecoveryRepairIsRecordedWithItsOwnAnswer; unsent repair diagnostics after cached pre-stripping and after a stripped retry, including prior account/cipher failures; and directly affected single-repair, stripped-retry availability, exact sent-body, account mismatch and account-failure classification cases. Formatting and diff whitespace checks pass. The normal required PR checks validate this new head separately.

No real model calls were made. Acceptance by api.openai.com of reasoning items without ID or ciphertext remains unverified; a rejected repair ends with that upstream response, and subsequent requests retain their normal first-send behavior.

Codex and others added 3 commits October 7, 2026 11:15
…a stripped reasoning item

Reasoning recovery deletes encrypted_content from reasoning items and keeps
the item with its id. An upstream may take such an item for a reference to
one it stores, and answers `Item with id '<id>' not found` when it has none
(HTTP 404 from api.openai.com). In production on 2026-10-06 one conversation
ended this way 59 times: 56 first sends that had been stripped from the 24h
memory and 3 stripped retries, all naming one id of the form rs_ + 32 hex.
Ids minted by OpenAI were accepted when left behind in the same way.

When the upstream's error message names an item this attempt stripped, the
request is now re-sent once on the same account with the id removed from the
items the attempt stripped; the items and every other field stay. This is the
remedy the Codex OAuth path already applies to every reasoning item.

- Trigger: an answer with a status >= 400 other than 401/402/403/407/429 whose
  message names, as not found, the id of a reasoning item that this attempt
  stripped and that was sent with that id and without ciphertext. An item
  without a summary keeps its id, because the upstream refuses it without one.
- It is separate from the stripped retry and does not spend it: an account
  sees at most three POSTs (first send, stripped retry, id repair). A strip
  made after the repair removes the id together with the ciphertext.
- Nothing is remembered. Each request that meets the answer repairs again.
- Native Responses, passthrough and Responses-shaped bodies on the Chat
  Completions endpoint. The repaired answer has no account-health effect.
- The wire-to-clean projection accepts the id deletion and nothing else new.
- Ops: recovery action `retry_without_item_ids`; the continuation diagnostic
  gains `recovery.item_ids_removed`.

Whether api.openai.com accepts a reasoning item with neither id nor
ciphertext was not verified against the real upstream. If it does not, the
repaired request ends with that answer instead of the 404.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… retry's record

Follow-up to the id repair, from review.

- Deleting a field from the stripped reasoning items edited the whole body
  once per item, in the strip, in the repair and again in the projection.
  At the sizes seen in production (hundreds of items, megabytes) that is
  hundreds of milliseconds to seconds of CPU per request. The items are now
  edited on their own and the body is written once; the per-item lookups read
  the input array once. The bytes are those of the one-by-one deletion, which
  is kept as the fallback and as the oracle of the new equivalence test.
- The repair no longer resets the record of the stripped retry
  (retryDispatched, diagnosticState): the continuation diagnostic goes on
  describing the stripped retry, as the handbook says.
- The repaired answer's status and request id are recorded with its own event
  and are not left in the state, which matters for the Chat Completions path
  that does not observe each response.
- Handbook: the bound of three POSTs is per forwarding attempt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@HTExplicit
HTExplicit merged commit 09027e0 into main Oct 7, 2026
18 checks passed
@HTExplicit
HTExplicit deleted the fix/stripped-reasoning-item-not-found branch October 7, 2026 07:56
HTExplicit added a commit that referenced this pull request Oct 7, 2026
The deployment path still carried retired acceptance, rollback, resource-request and plugin-era controls, while console typography needed to be brought up to date with the current upstream style. This integrates the remaining authorized takeover work after the reasoning repair in #252.

- Integrates #253, #254, #255 and #222 while retaining their commit ancestry. Removes stale release scaffolding and deployment template settings; preserves every existing migration SQL file.
- Production Deploy accepts only `release_tag` and `confirmation=DEPLOY`, sends `deploy <immutable-image>`, and retains standard OCI provenance and fixed SSH/environment controls. Both automation callers and historical-run deduplication match it.
- Migration 274 deletes only validated, soft-deleted release-acceptance keys and their usage/dedup rows, removes the two acceptance columns, and leaves ordinary keys and diagnostic logs. The matching application no longer reads those columns. Older images cannot be deployed after 274; failures require a forward fix.
- Uses upstream system fonts, sizes and readable semantic text colors without dropping table columns. Fixes the TLS empty-state color and whole-badge/word wrapping, and normalizes the audit helper's LF bytes without semantic changes.
- Removes the image-side skill-registry directory after the host mount and retired runtime configuration were removed.

Validation: reuse the component branches' directly affected tests and source-identical UI matrix; normal required checks run on this final integration tree. The only integration conflicts were the migration CI selector and retired documentation/notices; the resolved selector retains the new PostgreSQL migration fixture and live migration tests. All existing migration SQL is unchanged. Entry-point Bash syntax and diff checks pass.

The simplified host tool has completed a successful unchanged-image `.12` redeploy, preserving environment/resources and other containers. Host config retirement also passed with a new complete backup retained. UI localhost fake-data validation sampled 7,728 normal text/input/placeholder elements across 36 after states, with no sample below 4.5:1; account tables fit at 1280/1440/1920 in both languages/themes. Before screenshots block external fonts and show their fallback stack. No real model call or live upstream ID-less-item acceptance test was performed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant