Skip to content

fix: export the deferred trailing turn on SessionEnd - #3

Merged
caiohasouza merged 1 commit into
mainfrom
fix/export-trailing-turn-on-session-end
Sep 1, 2026
Merged

fix: export the deferred trailing turn on SessionEnd#3
caiohasouza merged 1 commit into
mainfrom
fix/export-trailing-turn-on-session-end

Conversation

@caiohasouza

Copy link
Copy Markdown
Owner

Problema

O deferral do turno final (nosso PR #1, equivalente ao #31 upstream) resolveu o trace duplicado, mas criou o lado oposto: o último turno de toda sessão nunca é exportado, e uma sessão de turno único não gera trace nenhum.

O Stop dispara depois de cada turno, e o convertRollout pula o turno cujo task_complete ainda não foi anexado, apostando que "a later hook invocation will export the finalized source turn". Para todo turno menos o último isso vale. Para o último, não existe invocação posterior.

Evidência

25 sessões reais do Codex em 2026-08-31. O ledger do sidecar grava exatamente turnos - 1 entradas:

turnos sidecar turnIds gravados
27 (+2 abortados) SIM 28
13 SIM 13
3 SIM 2
2 SIM 1
1 NÃO 0

Treze sessões de turno único naquele dia: todas sem sidecar e sem nenhuma observation no Langfuse. A falha é invisível — o hook sai com 0 e falha aberto por design.

Correção

O Codex despacha um hook SessionEnd e faz flush do transcript antes de disparar (string no binário: failed to flush transcript before SessionEnd hook). Logo, no SessionEnd o turno final já está completo.

Declarar o mesmo bundle em SessionEnd fecha a lacuna. O ledger do sidecar já garante exatamente-uma-vez: turnos exportados por um Stop anterior são pulados.

Sem mudança em src/ — o entrypoint ignora hook_event_name e apenas converte o rollout.

Validação

  • pnpm test — 40 testes (3 novos cobrindo o hook SessionEnd)
  • prettier --check e tsc --noEmit limpos
  • bundle commitado reproduz do fonte sem diff
  • recuperação manual das 3 sessões afetadas confirmou o mecanismo: reexecutar o bundle com o rollout já finalizado exporta o turno que faltava

Atenção ao instalar

hooks.json mudou, então o trusted_hash cai: é preciso reaprovar o hook em /hooks depois de atualizar. Versão vai de 0.1.1 para 0.1.2 (o path do bundle embute a versão).

🤖 Generated with Claude Code

https://claude.ai/code/session_0153mpaia6pZ4oYtT479U8kF

Deferring the trailing turn fixed duplicate traces, but nothing re-runs after
the last turn of a session, so that turn is never exported. A session with a
single turn produces no trace at all.

The Stop hook fires after every turn, and `convertRollout` skips a turn whose
`task_complete` has not been appended yet, on the assumption that "a later hook
invocation will export the finalized source turn". For every turn but the last
that assumption holds. For the last one there is no later invocation.

Measured on 25 real Codex sessions from 2026-08-31, the sidecar ledger records
exactly `turns - 1` entries in every multi-turn session, and single-turn
sessions get no sidecar and no observations at all: 13 of them that day, all
silent. The failure is invisible — the hook exits 0 and fails open by design.

Codex dispatches a `SessionEnd` hook and flushes the transcript before doing so
("failed to flush transcript before SessionEnd hook"), so the trailing turn is
complete by the time it runs. Declaring the same bundle on `SessionEnd` closes
the gap; the sidecar ledger already makes it exactly-once, so turns exported by
an earlier Stop are skipped.

No source change: the entrypoint ignores `hook_event_name` and simply converts
the rollout.

Verified: 40 tests pass, prettier and tsc clean, and the committed bundle still
reproduces from source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153mpaia6pZ4oYtT479U8kF
@caiohasouza
caiohasouza merged commit 8a28ddd into main Sep 1, 2026
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