fix: export the deferred trailing turn on SessionEnd - #3
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Stopdispara depois de cada turno, e oconvertRolloutpula o turno cujotask_completeainda 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 - 1entradas: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
SessionEnde faz flush do transcript antes de disparar (string no binário:failed to flush transcript before SessionEnd hook). Logo, noSessionEndo turno final já está completo.Declarar o mesmo bundle em
SessionEndfecha a lacuna. O ledger do sidecar já garante exatamente-uma-vez: turnos exportados por umStopanterior são pulados.Sem mudança em
src/— o entrypoint ignorahook_event_namee apenas converte o rollout.Validação
pnpm test— 40 testes (3 novos cobrindo o hookSessionEnd)prettier --checketsc --noEmitlimposAtenção ao instalar
hooks.jsonmudou, então otrusted_hashcai: é preciso reaprovar o hook em/hooksdepois de atualizar. Versão vai de0.1.1para0.1.2(o path do bundle embute a versão).🤖 Generated with Claude Code
https://claude.ai/code/session_0153mpaia6pZ4oYtT479U8kF