Repository navigation
fix(sse): prevent Anthropic 400s for Claude-native handoffs - #12668
diegosouzapw merged 3 commits into
Conversation
…g conversation (#12646) Validado sobre o tip de `release/v3.8.51`, com duas coisas resolvidas antes do merge. **A falha de CI era stale.** O job `No new ESLint warnings` deste PR apontava `react-hooks/set-state-in-effect` em `src/app/(dashboard)/dashboard/combos/page.tsx:774` — arquivo que este PR não toca, e o mesmo erro aparecia em #12668 e #12672, que também não o tocam. A linha do tempo: o #12355 introduziu a violação de manhã, os CIs rodaram nessa janela, e o #12607 acrescentou a entrada de supressão à tarde. Medido no tip atual com o comando exato do job: **0 ocorrências não suprimidas**. A supressão sobrevivente é "unpruned", e o script passa `--pass-on-unpruned-suppressions` justamente para isso não bloquear. **Faltava o teste que a regra do projeto exige** para mudanças em `src/`. Acrescentei `tests/unit/ui/log-detail-conversation-link-12646.test.tsx`, verificado **RED-then-GREEN** em vez de escrito contra o código pronto: revertendo `RequestLoggerDetail.sections.tsx` para o tip, 2 dos 3 casos falham; com a mudança deste PR, 3/3 passam. Detalhe que valeu a pena descobrir: a seção curto-circuita em `allTurns.length === 0`, então o fixture precisa de um `requestBody` que normalize em pelo menos um turno — sem isso o cabeçalho inteiro nunca monta e as asserções passariam pelo motivo errado. O teste fixa três coisas: o href para um `sessionTag` simples, o percent-encoding para um que não é URL-safe, e a ausência de link quando não há `sessionTag`. Obrigado, @hartmark.
|
Thanks for the detailed TDD writeup and live-repro evidence — this is a real Anthropic 400 |
df4ed00 to
452fa6a
Compare
|
Rebased onto the current
Rebase note: the universal-handoff injection call site moved out of Re #12735/#13355 merge-order coordination: after the rebase there is no file overlap with either PR (#12735 touches |
|
Rebased onto the current Gates this PR is responsible for are green on the latest run ( The remaining red checks reproduce from files this PR does not touch — they are pre-existing on the release tip:
None of these files appear in this PR's diff. Happy to split any of them into a separate fix PR if useful. |
Keep the Claude wire-body guard while staying under the frozen per-file line baselines and the complexity ratchets measured against release/v3.8.51. Extract the final constraint coordinator, split system-message normalization into focused helpers, and isolate handoff response parsing. Reflow the universal-handoff explanation to absorb the added source-format argument without growing the frozen file.
77a3fed to
b53f15f
Compare
c97f61b
into
cheaper-inference:release/v3.8.51
…g conversation (cheaper-inference#12646) Validado sobre o tip de `release/v3.8.51`, com duas coisas resolvidas antes do merge. **A falha de CI era stale.** O job `No new ESLint warnings` deste PR apontava `react-hooks/set-state-in-effect` em `src/app/(dashboard)/dashboard/combos/page.tsx:774` — arquivo que este PR não toca, e o mesmo erro aparecia em cheaper-inference#12668 e cheaper-inference#12672, que também não o tocam. A linha do tempo: o cheaper-inference#12355 introduziu a violação de manhã, os CIs rodaram nessa janela, e o cheaper-inference#12607 acrescentou a entrada de supressão à tarde. Medido no tip atual com o comando exato do job: **0 ocorrências não suprimidas**. A supressão sobrevivente é "unpruned", e o script passa `--pass-on-unpruned-suppressions` justamente para isso não bloquear. **Faltava o teste que a regra do projeto exige** para mudanças em `src/`. Acrescentei `tests/unit/ui/log-detail-conversation-link-12646.test.tsx`, verificado **RED-then-GREEN** em vez de escrito contra o código pronto: revertendo `RequestLoggerDetail.sections.tsx` para o tip, 2 dos 3 casos falham; com a mudança deste PR, 3/3 passam. Detalhe que valeu a pena descobrir: a seção curto-circuita em `allTurns.length === 0`, então o fixture precisa de um `requestBody` que normalize em pelo menos um turno — sem isso o cabeçalho inteiro nunca monta e as asserções passariam pelo motivo errado. O teste fixa três coisas: o href para um `sessionTag` simples, o percent-encoding para um que não é URL-safe, e a ausência de link quando não há `sessionTag`. Obrigado, @hartmark.
…inference#12668) * fix(sse): prevent Anthropic 400s for Claude-native handoffs * docs(changelog): add fragment for Claude-native handoff 400 fix * refactor(sse): satisfy file-size and complexity ratchets Keep the Claude wire-body guard while staying under the frozen per-file line baselines and the complexity ratchets measured against release/v3.8.51. Extract the final constraint coordinator, split system-message normalization into focused helpers, and isolate handoff response parsing. Reflow the universal-handoff explanation to absorb the added source-format argument without growing the frozen file.
Summary
systemparameter instead of inserting a non-emptyrole: "system"message atmessages[0].output_configenvelopes, and relocates those directives away from the forbidden first position.Anthropic rejects the previous Claude-native wire shape with HTTP 400 because initial system prompts belong in the top-level
systemparameter. The directive-only form (content: []withoutput_config) remains supported and is handled separately.Related Issues
Validation
Change type and focused loop from the Contribution Golden Path:
node --import tsx/esm --test tests/unit/context-handoff.test.ts tests/unit/universal-handoff.test.ts tests/unit/claude-directive-final-dispatch.test.ts tests/unit/claude-directive-only-relocation.test.ts tests/unit/claude-directive-midconv-passthrough.test.ts tests/unit/claude-system-role-cache-boundary.test.ts tests/unit/claude-code-compatible-request.test.ts tests/unit/chatcore-claude-upstream-messages.test.ts— 113 passednpm run typecheck:corenpm run test:vitest— 51 files, 465 tests passedgit diff --checkrelease/v3.8.51tip (910f58c5c); focused checks rerun afterwardnpm run lint— inherited failure atsrc/app/(dashboard)/dashboard/combos/page.tsx:774; the file is untouched by this PRnpm run test:unit/npm run test:coverage— long local runs were externally terminated after more than 20 minutes. Before termination, failures were limited to unrelated environment/performance tests and reproduced independently of the changed modules. CI remains authoritative for the complete runs.release/v3.8.51advanced from910f58c5cto2265ce761through the public-error-boundary campaign. On clean base SHA2265ce761(without this PR), the exact failing files reproduce: dashboard request-failure redaction, generic stream public error boundaries, HuggingChat transport redaction, Kiro Responses stream errors, model-sync invalid-JSON handling, and tunnel-error sanitization.API Route TypecheckandNo new ESLint warningsare likewise red on unrelated/base runs; none of their failing files are modified here.Tests Added Or Updated
tests/unit/context-handoff.test.tstests/unit/universal-handoff.test.tstests/unit/claude-directive-final-dispatch.test.tsCoverage Notes
systemplacement and the unchanged OpenAI message-based path.output_configprecedence.Reviewer Notes
The two repair layers are intentional:
The guard order matters: prompt-bearing leading system messages are hoisted first, then any directive-only envelope exposed at
messages[0]is relocated, then existing thinking constraints are normalized.No migrations, feature flags, provider weights, combo policy, credentials, or deployment configuration are changed.