mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-11 15:39:04 +02:00
fix(codex): close the consult-mode fence, report turn.failed as a failure, capture exit codes portably (#2671, #2669)
Three defects in the codex skill sections:
- The resumed-session bash block never closed its fence; every fenced region
after it inverted (prose rendered as code, the synthesis-recommendation tail
rendered inert). A repo-wide fence-pairing test now scans every generated
SKILL.md and sections/*.md with a CommonMark-faithful state machine (an
info-string opener inside a fence is literal content — nested template
examples in document-generate/make-pdf stay legal; a file ending inside a
fence fails).
- The JSONL parsers had no turn.failed branch: a turn that STATED its failure
was reported as 'possible mid-stream disconnect'. Challenge and consult now
print the event's error and run a three-way completeness check (failed-with-
reason / silent-disconnect / ok); consult previously had no completeness
check at all.
- ${PIPESTATUS[0]} is empty under zsh, so hang detection never fired and
every clean run printed a spurious '[codex exit ]'. All three capture sites
use ${PIPESTATUS[0]:-${pipestatus[1]}}, pinned statically and EXECUTED
under real bash and zsh in the new test. Expect a step-change in
codex_timeout telemetry — the counter starts firing for zsh users.
Receipt: the portability pin fails on a v1.77.0.0 scratch worktree; the fence
fix is structural (17 → 18 fence lines, tail no longer inside a block).
Fixes #2671
Fixes #2669
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
262a605794
commit
9ca92161e2
@@ -61,12 +61,19 @@ for line in sys.stdin:
|
||||
usage = obj.get('usage',{})
|
||||
tokens = usage.get('input_tokens',0) + usage.get('output_tokens',0)
|
||||
if tokens: print(f'\ntokens used: {tokens}', flush=True)
|
||||
elif t == 'turn.failed':
|
||||
turn_failed = True
|
||||
err = obj.get('error',{}).get('message','') or 'no error message in event'
|
||||
print(f'[codex turn FAILED] {err}', flush=True, file=sys.stderr)
|
||||
except: pass
|
||||
# Fix 2: completeness check — warn if no turn.completed received
|
||||
if turn_completed_count == 0:
|
||||
# Fix 2: three-way completeness check (#2671) — a STATED failure is a failure,
|
||||
# not a network problem; only silence with no terminal event is a disconnect.
|
||||
if 'turn_failed' in dir():
|
||||
print('[codex] turn.failed received — the turn errored (reason above), not a disconnect.', flush=True, file=sys.stderr)
|
||||
elif turn_completed_count == 0:
|
||||
print('[codex warning] No turn.completed event received — possible mid-stream disconnect.', flush=True, file=sys.stderr)
|
||||
"
|
||||
_CODEX_EXIT=${PIPESTATUS[0]}
|
||||
_CODEX_EXIT=${PIPESTATUS[0]:-${pipestatus[1]}} # bash sets PIPESTATUS; zsh (lowercase, 1-indexed) falls through (#2669)
|
||||
# Fix 1: hang detection — log + surface actionable message
|
||||
if [ "$_CODEX_EXIT" = "124" ]; then
|
||||
_gstack_codex_log_event "codex_timeout" "600"
|
||||
|
||||
@@ -59,12 +59,19 @@ for line in sys.stdin:
|
||||
usage = obj.get('usage',{})
|
||||
tokens = usage.get('input_tokens',0) + usage.get('output_tokens',0)
|
||||
if tokens: print(f'\ntokens used: {tokens}', flush=True)
|
||||
elif t == 'turn.failed':
|
||||
turn_failed = True
|
||||
err = obj.get('error',{}).get('message','') or 'no error message in event'
|
||||
print(f'[codex turn FAILED] {err}', flush=True, file=sys.stderr)
|
||||
except: pass
|
||||
# Fix 2: completeness check — warn if no turn.completed received
|
||||
if turn_completed_count == 0:
|
||||
# Fix 2: three-way completeness check (#2671) — a STATED failure is a failure,
|
||||
# not a network problem; only silence with no terminal event is a disconnect.
|
||||
if 'turn_failed' in dir():
|
||||
print('[codex] turn.failed received — the turn errored (reason above), not a disconnect.', flush=True, file=sys.stderr)
|
||||
elif turn_completed_count == 0:
|
||||
print('[codex warning] No turn.completed event received — possible mid-stream disconnect.', flush=True, file=sys.stderr)
|
||||
"
|
||||
_CODEX_EXIT=${PIPESTATUS[0]}
|
||||
_CODEX_EXIT=${PIPESTATUS[0]:-${pipestatus[1]}} # bash sets PIPESTATUS; zsh (lowercase, 1-indexed) falls through (#2669)
|
||||
# Fix 1: hang detection — log + surface actionable message
|
||||
if [ "$_CODEX_EXIT" = "124" ]; then
|
||||
_gstack_codex_log_event "codex_timeout" "600"
|
||||
|
||||
@@ -102,13 +102,24 @@ for line in sys.stdin:
|
||||
cmd = item.get('command','')
|
||||
if cmd: print(f'[codex ran] {cmd}', flush=True)
|
||||
elif t == 'turn.completed':
|
||||
turn_completed_count = 1 + (turn_completed_count if 'turn_completed_count' in dir() else 0)
|
||||
usage = obj.get('usage',{})
|
||||
tokens = usage.get('input_tokens',0) + usage.get('output_tokens',0)
|
||||
if tokens: print(f'\ntokens used: {tokens}', flush=True)
|
||||
elif t == 'turn.failed':
|
||||
turn_failed = True
|
||||
err = obj.get('error',{}).get('message','') or 'no error message in event'
|
||||
print(f'[codex turn FAILED] {err}', flush=True, file=sys.stderr)
|
||||
except: pass
|
||||
# Three-way completeness check (#2671; consult previously had NONE): a STATED
|
||||
# failure is a failure, not a network problem; only silence is a disconnect.
|
||||
if 'turn_failed' in dir():
|
||||
print('[codex] turn.failed received — the turn errored (reason above), not a disconnect.', flush=True, file=sys.stderr)
|
||||
elif 'turn_completed_count' not in dir():
|
||||
print('[codex warning] No turn.completed event received — possible mid-stream disconnect.', flush=True, file=sys.stderr)
|
||||
"
|
||||
# Fix 1: hang detection for Consult new-session (mirrors Challenge + resume)
|
||||
_CODEX_EXIT=${PIPESTATUS[0]}
|
||||
_CODEX_EXIT=${PIPESTATUS[0]:-${pipestatus[1]}} # bash sets PIPESTATUS; zsh (lowercase, 1-indexed) falls through (#2669)
|
||||
if [ "$_CODEX_EXIT" = "124" ]; then
|
||||
_gstack_codex_log_event "codex_timeout" "600"
|
||||
_gstack_codex_log_hang "consult" "$(wc -c < "$TMPERR" 2>/dev/null || echo 0)"
|
||||
@@ -144,7 +155,7 @@ _gstack_codex_timeout_wrapper 600 codex exec resume <session-id> "<prompt>" -c '
|
||||
<same python streaming parser as above, with flush=True on all print() calls>
|
||||
"
|
||||
# Fix 1: same hang detection pattern as new-session block
|
||||
_CODEX_EXIT=${PIPESTATUS[0]}
|
||||
_CODEX_EXIT=${PIPESTATUS[0]:-${pipestatus[1]}} # bash sets PIPESTATUS; zsh (lowercase, 1-indexed) falls through (#2669)
|
||||
if [ "$_CODEX_EXIT" = "124" ]; then
|
||||
_gstack_codex_log_event "codex_timeout" "600"
|
||||
_gstack_codex_log_hang "consult-resume" "$(wc -c < "$TMPERR" 2>/dev/null || echo 0)"
|
||||
@@ -156,6 +167,7 @@ elif [ "$_CODEX_EXIT" != "0" ]; then
|
||||
head -20 "$TMPERR" 2>/dev/null | sed 's/^/ /' || true
|
||||
_gstack_codex_log_event "codex_nonzero_exit" "consult-resume:$_CODEX_EXIT"
|
||||
fi
|
||||
```
|
||||
|
||||
5. Capture session ID from the streamed output. The parser prints `SESSION_ID:<id>`
|
||||
from the `thread.started` event. Save it for follow-ups:
|
||||
|
||||
@@ -100,13 +100,24 @@ for line in sys.stdin:
|
||||
cmd = item.get('command','')
|
||||
if cmd: print(f'[codex ran] {cmd}', flush=True)
|
||||
elif t == 'turn.completed':
|
||||
turn_completed_count = 1 + (turn_completed_count if 'turn_completed_count' in dir() else 0)
|
||||
usage = obj.get('usage',{})
|
||||
tokens = usage.get('input_tokens',0) + usage.get('output_tokens',0)
|
||||
if tokens: print(f'\ntokens used: {tokens}', flush=True)
|
||||
elif t == 'turn.failed':
|
||||
turn_failed = True
|
||||
err = obj.get('error',{}).get('message','') or 'no error message in event'
|
||||
print(f'[codex turn FAILED] {err}', flush=True, file=sys.stderr)
|
||||
except: pass
|
||||
# Three-way completeness check (#2671; consult previously had NONE): a STATED
|
||||
# failure is a failure, not a network problem; only silence is a disconnect.
|
||||
if 'turn_failed' in dir():
|
||||
print('[codex] turn.failed received — the turn errored (reason above), not a disconnect.', flush=True, file=sys.stderr)
|
||||
elif 'turn_completed_count' not in dir():
|
||||
print('[codex warning] No turn.completed event received — possible mid-stream disconnect.', flush=True, file=sys.stderr)
|
||||
"
|
||||
# Fix 1: hang detection for Consult new-session (mirrors Challenge + resume)
|
||||
_CODEX_EXIT=${PIPESTATUS[0]}
|
||||
_CODEX_EXIT=${PIPESTATUS[0]:-${pipestatus[1]}} # bash sets PIPESTATUS; zsh (lowercase, 1-indexed) falls through (#2669)
|
||||
if [ "$_CODEX_EXIT" = "124" ]; then
|
||||
_gstack_codex_log_event "codex_timeout" "600"
|
||||
_gstack_codex_log_hang "consult" "$(wc -c < "$TMPERR" 2>/dev/null || echo 0)"
|
||||
@@ -142,7 +153,7 @@ _gstack_codex_timeout_wrapper 600 codex exec resume <session-id> "<prompt>" -c '
|
||||
<same python streaming parser as above, with flush=True on all print() calls>
|
||||
"
|
||||
# Fix 1: same hang detection pattern as new-session block
|
||||
_CODEX_EXIT=${PIPESTATUS[0]}
|
||||
_CODEX_EXIT=${PIPESTATUS[0]:-${pipestatus[1]}} # bash sets PIPESTATUS; zsh (lowercase, 1-indexed) falls through (#2669)
|
||||
if [ "$_CODEX_EXIT" = "124" ]; then
|
||||
_gstack_codex_log_event "codex_timeout" "600"
|
||||
_gstack_codex_log_hang "consult-resume" "$(wc -c < "$TMPERR" 2>/dev/null || echo 0)"
|
||||
@@ -154,6 +165,7 @@ elif [ "$_CODEX_EXIT" != "0" ]; then
|
||||
head -20 "$TMPERR" 2>/dev/null | sed 's/^/ /' || true
|
||||
_gstack_codex_log_event "codex_nonzero_exit" "consult-resume:$_CODEX_EXIT"
|
||||
fi
|
||||
```
|
||||
|
||||
5. Capture session ID from the streamed output. The parser prints `SESSION_ID:<id>`
|
||||
from the `thread.started` event. Save it for follow-ups:
|
||||
|
||||
Reference in New Issue
Block a user