Skip to content

Commit f979848

Browse files
feat: add standalone probe binaries + JSONL parser for /codex and /autoplan (issue #1329)
Extracts the logic from gstack-codex-probe bash functions and the inline Python JSONL streaming parser into individually-executable binaries so skill templates can call them directly without triggering Claude Code PreToolUse security hooks. New binaries (all in bin/): - gstack-codex-auth-probe: multi-signal auth check (exit 0=ok, 1=failed) - gstack-codex-version-check: warns on known-bad Codex CLI versions - gstack-codex-log-event: telemetry emitter (reads config internally, no _TEL env) - gstack-codex-log-hang: operational learning writer on codex timeout - gstack-codex-timeout-wrapper: gtimeout/timeout/unwrapped fallback chain - gstack-codex-jsonl-parser: Python script parsing codex --json streaming output with --mode challenge (completeness check) and --mode consult (SESSION_ID extract) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 40e34de commit f979848

6 files changed

Lines changed: 145 additions & 0 deletions

bin/gstack-codex-auth-probe

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env bash
2+
# gstack-codex-auth-probe: standalone auth check. Prints AUTH_OK (exit 0) or
3+
# AUTH_FAILED (exit 1). Replaces _gstack_codex_auth_probe() from gstack-codex-probe
4+
# for skill templates that cannot safely source the probe file.
5+
#
6+
# Multi-signal: env vars OR auth file. Avoids false negatives for env-auth users
7+
# (CI, platform engineers) that a file-only check would reject.
8+
_codex_home="${CODEX_HOME:-$HOME/.codex}"
9+
_k1=$(printf '%s' "${CODEX_API_KEY:-}" | tr -d '[:space:]')
10+
_k2=$(printf '%s' "${OPENAI_API_KEY:-}" | tr -d '[:space:]')
11+
if [ -n "$_k1" ] || [ -n "$_k2" ] || [ -f "$_codex_home/auth.json" ]; then
12+
echo "AUTH_OK"
13+
exit 0
14+
fi
15+
echo "AUTH_FAILED"
16+
exit 1

bin/gstack-codex-jsonl-parser

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env python3
2+
"""
3+
gstack-codex-jsonl-parser: parse Codex CLI --json streaming output to human-readable form.
4+
5+
Usage: gstack-codex-jsonl-parser [--mode challenge|consult]
6+
Reads JSONL from stdin (codex exec --json output), prints formatted lines to stdout.
7+
8+
Modes:
9+
challenge Track turn.completed count; warn on disconnect (no events received).
10+
consult Extract SESSION_ID from thread.started for follow-up sessions.
11+
"""
12+
import sys
13+
import json
14+
15+
mode = "consult"
16+
args = sys.argv[1:]
17+
i = 0
18+
while i < len(args):
19+
if args[i] == "--mode" and i + 1 < len(args):
20+
mode = args[i + 1]
21+
i += 2
22+
else:
23+
i += 1
24+
25+
turn_completed_count = 0
26+
for line in sys.stdin:
27+
line = line.strip()
28+
if not line:
29+
continue
30+
try:
31+
obj = json.loads(line)
32+
t = obj.get("type", "")
33+
if t == "thread.started" and mode == "consult":
34+
tid = obj.get("thread_id", "")
35+
if tid:
36+
print(f"SESSION_ID:{tid}", flush=True)
37+
elif t == "item.completed" and "item" in obj:
38+
item = obj["item"]
39+
itype = item.get("type", "")
40+
text = item.get("text", "")
41+
if itype == "reasoning" and text:
42+
print(f"[codex thinking] {text}", flush=True)
43+
print(flush=True)
44+
elif itype == "agent_message" and text:
45+
print(text, flush=True)
46+
elif itype == "command_execution":
47+
cmd = item.get("command", "")
48+
if cmd:
49+
print(f"[codex ran] {cmd}", flush=True)
50+
elif t == "turn.completed":
51+
turn_completed_count += 1
52+
usage = obj.get("usage", {})
53+
tokens = usage.get("input_tokens", 0) + usage.get("output_tokens", 0)
54+
if tokens:
55+
print(f"\ntokens used: {tokens}", flush=True)
56+
except Exception:
57+
pass
58+
59+
if mode == "challenge" and turn_completed_count == 0:
60+
print(
61+
"[codex warning] No turn.completed event received — possible mid-stream disconnect.",
62+
flush=True,
63+
file=sys.stderr,
64+
)

bin/gstack-codex-log-event

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env bash
2+
# gstack-codex-log-event: standalone telemetry emitter. Replaces
3+
# _gstack_codex_log_event() from gstack-codex-probe for skill templates
4+
# that cannot safely source the probe file.
5+
#
6+
# Usage: gstack-codex-log-event <event> [duration_s]
7+
# Event types: codex_timeout, codex_auth_failed, codex_cli_missing,
8+
# codex_version_warning.
9+
# Payload: {skill, event, duration_s, ts}. NEVER includes prompt content,
10+
# env var values, or auth tokens.
11+
_event="${1:-unknown}"
12+
_duration="${2:-0}"
13+
_bin_dir="$(cd "$(dirname "$0")" && pwd)"
14+
_TEL=$("$_bin_dir/gstack-config" get telemetry 2>/dev/null || echo "off")
15+
[ "$_TEL" = "off" ] && exit 0
16+
mkdir -p "$HOME/.gstack/analytics" 2>/dev/null || exit 0
17+
_ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo unknown)
18+
printf '{"skill":"codex","event":"%s","duration_s":"%s","ts":"%s"}\n' \
19+
"$_event" "$_duration" "$_ts" \
20+
>> "$HOME/.gstack/analytics/skill-usage.jsonl" 2>/dev/null || true

bin/gstack-codex-log-hang

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
# gstack-codex-log-hang: standalone hang logger. Replaces _gstack_codex_log_hang()
3+
# from gstack-codex-probe for skill templates that cannot safely source the probe.
4+
#
5+
# Usage: gstack-codex-log-hang <mode> [prompt_size]
6+
# Invoked when a codex invocation times out (exit 124). Records an operational
7+
# learning so future /investigate sessions surface the pattern. Best-effort.
8+
_mode="${1:-unknown}"
9+
_prompt_size="${2:-0}"
10+
_log_bin="$HOME/.claude/skills/gstack/bin/gstack-learnings-log"
11+
[ -x "$_log_bin" ] || exit 0
12+
_key="codex-hang-$(date +%s 2>/dev/null || echo unknown)"
13+
"$_log_bin" "$(printf '{"skill":"codex","type":"operational","key":"%s","insight":"Codex timed out after 600s during [%s] invocation. Prompt size: %s. Consider splitting prompt or checking network.","confidence":8,"source":"observed","files":["codex/SKILL.md.tmpl","autoplan/SKILL.md.tmpl"]}' "$_key" "$_mode" "$_prompt_size")" \
14+
>/dev/null 2>&1 || true

bin/gstack-codex-timeout-wrapper

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
# gstack-codex-timeout-wrapper: standalone timeout wrapper. Replaces
3+
# _gstack_codex_timeout_wrapper() from gstack-codex-probe for skill templates
4+
# that cannot safely source the probe file.
5+
#
6+
# Usage: gstack-codex-timeout-wrapper <duration_seconds> <command> [args...]
7+
# Resolves gtimeout (Homebrew coreutils on macOS) -> timeout (Linux) -> unwrapped.
8+
if [ "$#" -lt 2 ]; then
9+
echo "Usage: gstack-codex-timeout-wrapper <duration> <command> [args...]" >&2
10+
exit 1
11+
fi
12+
_duration="$1"
13+
shift
14+
_to=$(command -v gtimeout 2>/dev/null || command -v timeout 2>/dev/null || echo "")
15+
if [ -n "$_to" ]; then
16+
"$_to" "$_duration" "$@"
17+
else
18+
"$@"
19+
fi

bin/gstack-codex-version-check

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
# gstack-codex-version-check: standalone version check. Warns on known-bad Codex
3+
# CLI versions. Replaces _gstack_codex_version_check() from gstack-codex-probe.
4+
#
5+
# Anchored regex prevents false positives like 0.120.10 or 0.120.20.
6+
# Update the pattern when a new Codex CLI version regresses.
7+
_ver=$(codex --version 2>/dev/null | head -1)
8+
[ -z "$_ver" ] && exit 0
9+
if echo "$_ver" | grep -Eq '(^|[^0-9.])0\.120\.(0|1|2)([^0-9.]|$)'; then
10+
echo "WARN: Codex CLI $_ver has known stdin deadlock bugs. Run: npm install -g @openai/codex@latest"
11+
fi
12+
exit 0

0 commit comments

Comments
 (0)