Skip to content

Codex native compaction fallback forwards private bili markers and fails with HTTP 400 #769

Closed

Description

@z0d0dOr

A native Codex compaction fallback can forward bili's local compaction item to the upstream, causing HTTP 400 and preventing compaction from completing. This was observed in an automatic approval-review session with billion-context 0.1.109 on Windows.

The upstream error was (identifier sanitized):

Invalid 'input[6].id': 'fc_bili_<uuid>'. Expected an ID that begins with 'cmp'.

The proxy logged codex compaction_trigger request not intercepted (gate preconditions not met) — forwarding verbatim (no preflight, no rebuild, no window clamp) immediately before that response.

Reproduction

  1. Create a local forged compaction response using buildTriggerForgeBody(). Its item has an fc_bili_ ID and bili:acp: summary.
  2. Echo that item in a Codex Responses request, followed by a final compaction_trigger.
  3. Let the native fallback run, either because interception gate preconditions fail or because BILI_CODEX_COMPACT=pass.
  4. An upstream that validates native compaction IDs rejects the local item.

A focused HTTP regression reproduces the failure at input[6] in both modes without credentials or an external model.

Root cause and proposed fix

The trigger fallback in src/server.ts forwards the original request buffer before prepareResponses(), bypassing its replaceBiliCompactionItems() handling. Ordinary turns already convert the local item into a normal user message carrying the summary.

Apply that existing conversion only to the native trigger fallback's original wire input. Keep the original Buffer when no local items are present, preserve real cmp_* items and their opaque content, retain the final trigger, and avoid preflight or rebuilding the client's history. Parse the original buffer rather than reusing parsed, because a failed prepare/forge attempt may already have mutated it.

Renaming fc_bili_ to cmp_ would be incorrect: bili's plaintext summary is not an upstream-native encrypted compaction record.

Local verification

The two new strict-upstream regressions fail with HTTP 400 before the fix and pass after it. Existing byte-identical fallback and native-compaction completion/state tests pass; TypeScript checking and the bundle build pass. A fix PR will follow.

This issue concerns the confirmed trigger fallback. It does not claim that earlier capacity/time-out failures have the same cause or that changing approval policy is necessary.

Activity

  1. z0d0dOr commented on Sep 14, 2026

    @z0d0dOr
    Author

    Fix submitted in #770. Local regression tests, type checking, and build results are documented in the PR; real-client/four-phase upstream E2E remains outstanding before merge.

  2. ranxianglei commented on Sep 14, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    Review of PR #770 complete — I independently reproduced every validation claim the PR makes, plus a negative control. Verdict below.

    Duplicate screening: searched open+closed issues for fc_bili_, compaction trigger fallback, compaction 400 — no pre-existing report covers this defect. The only hits are #769/#770 themselves and the historical closed codex-compact family (#322–#325, #336) that introduced this code path. Not a duplicate.

    Bug triage (verified before touching anything):

    1. Does it exist? Yes. On master (origin/master = 391bede), I ran the PR's two new strict-upstream regressions against the pre-fix src/server.ts: both fail with exactly the reported error, in both fallback modes (gate-preconditions-not-met under intercept, and BILI_CODEX_COMPACT=pass):
      Invalid 'input[6].id': 'fc_bili_…'. Expected an ID that begins with 'cmp'.
    2. Layer: root cause confirmed as reported — the trigger fallback (src/server.ts:1630-1631) forwards the original bodyBuffer verbatim, dispatched before prepare/preflight, so it bypasses replaceBiliCompactionItems() which ordinary turns apply inside prepareResponses() (src/server.ts:2370). The OP's analysis is correct, not a symptom-level read.
    3. Fix assessment: the approach hits the core. Normalizing only the original wire input keeps the preflight fail-fast × soft-protection livelock: over-window payload never recovers, permanent 502 loop #332 verbatim invariant (no preflight/rebuild/window clamp), stays byte-identical when no bili items are present, preserves real cmp_* blobs and the final trigger, and re-parsing from bodyBuffer instead of reusing parsed is the right defensive choice since a failed forge attempt may have mutated parsed. The explicit rejection of renaming fc_bili_ → cmp_ is also correct — bili's plaintext summary is not a native encrypted compaction record.

    Independent verification (isolated worktree at PR head a3d30e3 = master + 1 clean commit):

    • npm run typecheck — pass
    • tests/codex-compact-e2e.test.ts tests/native-compact-terminal.test.ts — 13/13 pass (matches PR claim)
    • full npm test — 1461 pass / 0 fail / 2 skipped (default e2e gate)
    • npm run build — pass
    • Negative control (above): 2/2 new regressions fail pre-fix with the exact issue error at input[6] — these are real guards, not vacuous tests
    • Diff hygiene: 2 files, +54/−6, every line in service of the fix; no version change, no new dependencies, no lockfile churn; branch naming and Fixes #769 linkage compliant

    Outstanding before merge (unchanged from the PR's own note): the real-client four-phase E2E (tests/e2e/e2e-codex.test.ts) is required before merging request-pipeline changes per repo rules. The zero-token preflight passes here (codex binary + dist OK), but this sandbox has no reachable Responses-compatible upstream (default 127.0.0.1:8199 unreachable, no alternate credentials), so the full run remains outstanding — needs a host with a local upstream or the manual-dispatch CI workflow before merge. Merge itself stays human-only.

    Minor observation (report-only, out of scope for this PR): the same marker-leak class exists on another verbatim path — web-config passthrough mode gates the whole pipeline behind !opts.passthrough (src/server.ts:1194), so once compression is toggled off mid-session, any previously-forged fc_bili_ item echoed by codex would again be forwarded unconverted to a strict upstream. Narrow edge case (requires prior forge + passthrough toggle + strict upstream); flagging for owner judgment rather than opening a separate issue.

    中文摘要:已独立复现并验证该 bug(master 上两种 fallback 模式均精确复现 issue 中的 400 报错),PR #770 的修复定位到根因、diff 干净、全部本地验证(typecheck/单测/全量测试/构建)通过且负向对照确认新回归测试有效;合并前仅剩真实客户端四阶段 E2E 待在有上游环境执行,其余无阻塞。

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions