Skip to content

fix(anthropic): ping-aware stream watchdog — eliminate NonZeroAgentExitCodeError on large-context agentic runs#733

Closed
ericleepi314 wants to merge 331 commits into
mainfrom
fix/stream-watchdog-restream
Closed

fix(anthropic): ping-aware stream watchdog — eliminate NonZeroAgentExitCodeError on large-context agentic runs#733
ericleepi314 wants to merge 331 commits into
mainfrom
fix/stream-watchdog-restream

Conversation

@ericleepi314

Copy link
Copy Markdown
Collaborator

Summary

Fixes the dominant failure class in the terminal-bench 2.1 eval: 18/89 trials died with NonZeroAgentExitCodeError (mean 0.58 vs claude-code 0.72, which had zero such crashes). Root-caused to the stream idle watchdog + a Python-SDK behavior.

Root cause (verified at SDK source)

The Anthropic Python SDK drops ping keepalive events — _streaming.py:151 if sse.event == "ping": continue — so the watchdog, which iterates the typed event stream, sees dead air whenever the model works >90s between typed events. In the agentic loop every turn re-sends the growing conversation, so every stream call pays prompt-processing time-to-first-event, which scales toward minutes as context approaches opus-4-8's 1M window (the #730 1M-context fix made this common). The crash duration_ms clustered at 94–100s (just past the flat 90s window) is the fingerprint. Worse, the watchdog's recovery re-issued the request non-streaming, which the SDK refuses outright for opus-class max_tokens ("Streaming is required for operations that may take longer than 10 minutes") → fatal exit 1.

Fix (three complementary layers; TS-parity)

  1. Ping-aware liveness (primary). The dropped pings are still bytes on the wire that httpx.Response.num_bytes_downloaded counts (httpx/_models.py:952, incremented per raw chunk in iter_bytes before SSE-decoding; the SDK consumes response.iter_bytes()). On each deadline the watchdog checks byte progress and re-arms instead of firing when bytes advanced — firing only on true dead air. Matches the ping-aware TS watchdog. Zero changes to the accumulation path.
  2. Retry the stream, never downgrade to non-streaming. The fatal _fallback_to_chat is deleted; watchdog fire → bounded stream retry (stream_idle_max_attempts, default 3) → StreamIdleTimeout (harness-retryable phrasing) on exhaustion.
  3. Two-phase deadline (fallback). First event gets a 300s grace (prompt processing), later events 90s — the time-based behavior when the byte counter is unavailable.
    Plus: explicit chat() per-request timeout (API_TIMEOUT_MS, default 600s, TS getApiTimeoutMs) which also disarms the SDK's >10-min guard for legit non-streaming callers (compaction, session title, judges).

Test plan

  • Live re-smoke of 5 previously-crashing tasks (opus-4-8 + subscription + effort high), all of which died with NonZeroAgentExitCodeError in the 0.58 run:
    • dna-assembly1.0 PASS, feal-linear-cryptanalysis1.0 PASS
    • filter-js-from-html, adaptive-rejection-samplercomplete, reward 0.0, no exception (genuine model misses, not crashes)
    • write-compressorAgentTimeoutError (the legit slow-task mode claude-code also hits)
    • Zero trials died at a 90s-multiple → the watchdog false-positive is eliminated
  • regex-log (a former crash) → 1.0 PASS in an earlier stage
  • 119 passing across test_stream_watchdog / test_anthropic_thinking_keeps_stream_alive / test_providers / test_ch04_api_round4 / test_query_extended_thinking / test_context_runtime_limits (byte-progress re-arm + dead-air fire, retry-then-succeed, exhaustion-raises, chat timeout, two-phase transition)
  • SDK/httpx mechanism verified at library source
  • Critic: reviewed across 4 rounds → "Ship the branch"

🤖 Generated with Claude Code

ericleepi314 and others added 30 commits June 27, 2026 18:09
Port openclaude's /stickers (typescript/src/commands/stickers — it opens
stickermule.com/claudecode). Shows the sticker URL.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's global content search (inventory §6 GlobalSearchDialog) as a
/search <query> command: runs ripgrep in cwd (smart-case, capped) and shows
matching file:line results. Distinct from @ (filenames) and Ctrl+R (history).
Query is single-quoted for shell safety.

Verified: /search CompanionSprite returns the .tsx matches.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's clickable refs (inventory §8). hyperlink() wraps known refs
in OSC 8 escapes with terminal capability detection (iTerm2/WezTerm/kitty/ghostty/
vscode/Hyper/rio/VTE) — no-op elsewhere so unsupported terminals show no garbage.
Tool entries now link web args (URL) and file-op args (file:// resolved to cwd).

This reverses my "fiddly-marginal" call — the clean version wraps *known* refs
(not heuristic detection) + gates on capability.

Verified: supported terminal wraps file.ts in OSC 8; unsupported returns plain text.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's MessageSelector (inventory §6) as the no-arg /rewind: opens a
picker of past prompts ("Rewind to before <preview>") and rewinds the
conversation to that point. /rewind <N> still drops N turns directly. Shared
requestRewind() helper; picker kind 'rewindpick' maps each restore point to the
turn count to drop.

This coexists with /rewind <N> exactly as MessageSelector coexists with simple
rewind in the original (so it's faithful parity, not redundancy).

Verified: 3 prompts → /rewind opens the picker → selecting "before second task"
sends rewind turns=2.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…484)

Port the original's HistorySearchDialog (inventory §6) as /history: opens a
list-picker of submitted prompts (newest first) → recalls the chosen one into the
input. Distinct from Ctrl+R (inline cycle) — a browsable list view. Reuses the
picker (kind 'historyrecall').

Verified: after 2 prompts, /history lists them; selecting "beta query" recalls it
to the input.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's Settings dialog (inventory §6) as /settings: a picker of
configurable areas (model/mode/theme/effort/provider) that routes the selection
to the matching setter command. Picker Enter now closes before applyPick so a
selection can open a nested picker.

Verified: /settings lists the areas; selecting "model" opens the model picker.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's multiline input (inventory §1). A line ending in `\` + Enter
inserts a newline and keeps editing instead of submitting (terminal-independent,
unlike Shift/Meta+Enter). The input value renders multiline; the next plain Enter
submits the whole thing.

Verified: "line1\" + Enter does not submit (becomes "line1\n"); "line2" + Enter
submits "line1\nline2".

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's Shift+Tab mode cycle (inventory §5/§8) — previously deferred.
Shift+Tab (\x1b[Z) cycles default → acceptEdits → plan → bypassPermissions,
sends set_permission_mode, and updates the mode state (footer reflects it).

Verified: 3× Shift+Tab → acceptEdits → plan → bypassPermissions controls sent;
footer shows the mode.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's "Yes, and don't ask again" permission option (inventory §5).
Pressing 'a' (option 2) at a permission prompt remembers the tool in a session
allow-set and allows it; future requests for that tool are auto-allowed (an effect
responds + dequeues without showing the dialog). Client-side — no backend protocol
change. Dialog now lists Yes / Yes-don't-ask-again / No.

Verified: 'a' allows + remembers Bash; a second Bash request auto-allows with no
keypress and no dialog.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Complete the permission UX (inventory §5). At a prompt, Tab opens a feedback
field — typing "what to do differently" and pressing Enter denies the tool with
that message sent back to the agent (Esc cancels the field). The dialog now shows
the "tab to amend · esc to cancel" hint. Permission UX is now Yes / Yes-don't-ask
/ No / Tab-amend + Shift+Tab mode cycle.

Verified: Tab opens the field; "use sed instead" + Enter → deny with that message.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's CompactSummary (inventory §3): after /compact, show a
divider-style "── ✻ Summarized conversation · N → M messages · saved Xk tokens ──"
boundary marking the summarization point + direction, instead of a plain line.

Verified: /compact → "── ✻ Summarized conversation · 20 → 4 messages · saved 12.0k tokens ──".

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's DiffDialog (inventory §4). /diff now lists changed files
(git diff --name-only): 0 → "no changes", 1 → its diff, >1 → a file picker
(kind 'difffile') → selecting shows that file's diff. Replaces the single dump
with a navigable per-file browser.

Verified: with 2 changed files, /diff shows the picker; selecting renders that
file's diff.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's /tasks (inventory §2/§9) as a safe status view: shows the
in-flight turn (with its current tool activity) and the count of queued prompts,
or "no active tasks" when idle. Read-only snapshot of the async work the
agent-server already does (no concurrency change — true Ctrl+B turn-backgrounding
would need a multi-session backend, deferred).

Verified: /tasks → "Tasks: no active tasks" when idle.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the readline undo (inventory §1/§8) missing from the input. Ctrl+_ (\x1f)
reverts the last edit. An effect snapshots each value change onto an undo stack
(capped 200); Ctrl+_ pops it back, with an isUndoing guard so the undo's own
change isn't re-recorded. Works in both insert and vim modes.

Verified: typing "hello" then Ctrl+_ twice → "hell" → "hel".

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…#494)

Complete the readline op set (inventory §1/§8). Ctrl+T transposes the two chars
around the cursor; Alt+D kills from the cursor to the next word end (into the
kill-ring, yankable with Ctrl+Y). Joins the existing Ctrl+A/E/W/U/K/Y +
word-motion + undo.

Verified: "ab" + Ctrl+T → "ba"; "foo bar" at col 0 + Alt+D → "bar".

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's external-editor keybinding (inventory §8). Ctrl+G writes the
current prompt to a temp file, opens it in $VISUAL/$EDITOR (default vi), and reads
the edited text back into the input. Raw mode is dropped around the editor (with
a finally to always restore it) so the editor owns the terminal; a repaint
follows. editInEditor() is a pure helper for testability.

Verified: editInEditor("hello world") with a non-interactive uppercasing $EDITOR
→ "HELLO WORLD"; in-app Ctrl+G on "edit me" → "EDIT ME".

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Close much of the vim gap (inventory §1). Adds motions e (end-of-word), ^ (first
non-blank), G (last col); edits ~ (toggle case + advance), C (change to end), s
(substitute char); and an unnamed register (filled by x/D/s/C) pasted by p/P.
Joins the existing h/l/0/$/w/b + i/a/I/A + x/D.

Verified: ~ toggles "hello"→"Hello" at col 0; C from mid → "Hi"; 0 x → "bc";
p pastes the deleted char → "bac".

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Add operator-pending mode (inventory §1) — the core of vim editing. d/c/y wait
for a motion, then apply over the range: dw/db/de/d$/d0/d^/dG/dh/dl (delete),
c… (change → insert), y… (yank). Doubled form dd/cc/yy acts on the whole line.
All fill the unnamed register (pasteable with p/P).

Verified: "hello world" dw → "world"; d$ from col2 of "abcdef" → "ab"; cc then
"Q" → "Q"; yy then p → "heyhey".

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Add the char-pending vim commands (inventory §1): f<c>/F<c> jump to the next/prev
occurrence of <c>, t<c>/T<c> stop just before/after it, and r<c> replaces the char
under the cursor. Implemented via a pendingFind state that captures the next key.

Verified: "hello" → r J → "Jello"; fl then x → "Jelo".

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Add text objects to operator-pending mode (inventory §1). After d/c/y, the i/a
prefix selects an object: w (word), " ' ` (quotes), () [] {} <> (pairs) — inner
(i) or around (a). Enables diw/ciw, ci"/da", ci(/da(, etc. via a pendingTextObj
state + textObjectRange().

Verified: "foo bar" diw → " bar"; 'say "hi" ok' ci" then X → 'say "X" ok'.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Add count prefixes (inventory §1): digits accumulate into a count that repeats
the next motion (h/l/w/b/e) or x N times. A leading 0 stays the line-start motion;
0 mid-count is a digit. (Operators consume the count as 1 for now.)

Verified: "abcdefgh" 3x → "defgh"; "abcdef" 0 2l x → "abdef".

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's inline ghost-text (inventory §1): while typing a slash
command, the dim suffix of the top match is shown inline after the cursor (e.g.
"/sett" → "/sett" + dim "ings"). Tab/Enter accepts it via the existing menu.
Shown only for a single partial /token in non-vim mode.

Verified: typing "/sett" renders dim "ings" on the input line (→ /settings).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Wire the output-style end-to-end (inventory §2/§6; was non-functional). Backend:
new set_output_style control sets tool_context.output_style_name and rebuilds the
session system prompt (idle-guarded) so the style's "# Output Style" section is
appended on the next turn; validates against VALID_OUTPUT_STYLES. TUI: /output-style
opens a picker (default/concise/verbose/markdown) → set_output_style.

Verified: backend e2e — "concise" accepted, "bogus" rejected; TUI — picker →
set_output_style sent → "output style → concise". Server suite green.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Add the . repeat command (inventory §1) for buffer changes: x, D, ~, r, p/P, and
d-operator (motion + text object) record a pure replay closure (current
value/cursor → new), and . re-applies it at the cursor. Extracted motionTarget()
so the operator and the recorder share motion logic. Insert-changes (c/i/s) are
not yet recorded — a follow-up.

Verified: "one two three four" dw → "two three four", . → "three four"; x then .
chains single-char deletes.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Complete vim . (inventory §1): insert-entering changes now record a `.`-repeatable
change too. enterInsert(pre, start) arms capture on i/a/I/A/s/C/cc/c-motion/c-obj;
on Esc the typed text (insertStart..cursor) plus the pre-delete closure are stored
as lastChange, so . replays the whole change (delete + retype) at the cursor.

Verified: "foo bar baz" ciw X esc → "X bar baz", w . → "X X baz"; iZ esc → +Z,
. → repeats; dw . non-insert path still works.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Complete the readline kill-ring (inventory §1). Kills (Ctrl+W/U/K, Alt+D) now also
push to a kill-history; after Ctrl+Y, Meta+Y replaces the just-yanked text with the
next-older kill, cycling. Adjacency is tracked via a yankActive ref reset each
keystroke, so Meta+Y only cycles immediately after a yank. Ctrl+Y behavior is
otherwise unchanged.

Verified: kill "beta" then "alpha " → Ctrl+Y yanks "alpha " → Meta+Y → "beta".

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…sion (#506)

Port the original's Knowledge Graph engine (/knowledge) as a functional store.
Backend: src/knowledge (KnowledgeGraph — entities keyed by type:name with
count/last_seen, heuristic record_from_text for files/`symbols`/urls, JSON
persistence at ~/.clawcodex/knowledge/graph.json). The agent-server auto-records
from the latest exchange at each turn end (gated by _knowledge_enabled, default
on) and exposes a `knowledge` control (status/list/clear/enable/disable). TUI:
/knowledge [list|clear|enable|disable] shows counts + top entities.

Heuristic extraction now; the original's model-based semantic extraction + @orama
search is a follow-up. Store, persistence, recording, control, and command are
functional + tested end-to-end.

Verified: store unit tests (extract/count/persist/clear); backend e2e (status/
disable/list round-trip); TUI /knowledge list renders counts + entities.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Port the original's /wiki as a file-based project wiki under .clawcodex/wiki.
Backend: src/wiki (init creates pages/, sources/, index.md, log.md, schema.md +
a starter architecture.md; status counts pages/sources; ingest copies a file into
sources/ and logs it). agent-server `wiki` control routes init/status/ingest.
TUI: /wiki [init|status|ingest <path>].

Verified: wiki unit tests (init/idempotent/status/ingest/missing); backend e2e
(status→init→status); TUI /wiki init + status render.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
#508)

Make the knowledge graph faithful to the original's model-based extraction, opt-in.
src/knowledge/extract.py asks the provider for a JSON entity list ([{name,type}],
fenced/prose tolerant, []-on-error). agent-server gains _knowledge_semantic
(default off); when on, _record_knowledge uses model extraction and falls back to
the heuristic if it yields nothing. /knowledge semantic|heuristic toggles it;
status/list now report the mode and the TUI shows "· semantic".

Verified: extract unit tests (json/fences/unknown-type/bad-json/none); full
knowledge+wiki+server suite (101) green; TUI /knowledge semantic shows the mode.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
#509)

Port the concurrent core of the original's backgroundable runs (inventory §9).
Backend: src/background BackgroundTasks (thread-safe registry of detached shell
commands in subprocesses — start/list/output/kill, captured output, no
conversation race). agent-server bg_run/bg_list/bg_kill controls. TUI: /bg <cmd>
starts a background task, /bg kill <id> terminates, and /tasks now lists live
background tasks (icon + status + command) alongside the running turn + queue.

Full turn/agent backgrounding (concurrent conversations) remains a larger
multi-session effort; this lands the concurrent bash-task variant end-to-end.

Verified: registry unit tests (complete/failed/list/output/kill); backend e2e
(bg_run → poll bg_list → done + output); TUI /bg + /tasks render. Suite green.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
agentforce314 and others added 27 commits July 13, 2026 14:05
…codex paths (#710)

_prepare_subscription_request's clean() rewrote EVERY case-insensitive
"clawcodex" in the system prompt to "Claude Code" — including filesystem
paths. On Claude-subscription logins the model was told its memory lives at
~/.Claude Code/projects/<slug>/memory and its history at
~/.Claude Code/sessions — literal nonexistent locations it then searched
verbatim. This is the real source of the "guessed wrong paths" #706/#707
addressed: those fixes emit correct paths, and this rewrite corrupted them
after prompt assembly, at the provider layer.

Constrain the rewrite to standalone brand mentions: matches inside path
segments (~/.clawcodex, /etc/clawcodex), env vars ($CLAWCODEX_CONFIG_DIR),
module/file names (clawcodex_dirs), and domains (clawcodex.app) survive
verbatim. Prose disguise and the official-prefix block are unchanged.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…nfigs (#711)

Add the two latest Anthropic models across every model-config surface:

- models/configs.py: ModelConfig rows (1M context; 32K first-attempt wire
  max_tokens with the 128K true cap noted; Opus 4.8 at $5/$25,
  Fable 5 at $10/$50 per MTok with matching cache rates). Placed after
  the legacy opus row so prefix fallback for opus-4-1..4-7 is unchanged.
- services/pricing.py: new 10/50 tier for Fable 5; exact PRICING and
  _FAMILY_PREFIXES entries for both models (dated snapshots included).
- providers: both added to the anthropic available_models lists; `fable`
  added to the large-max-tokens pattern so Fable 5 isn't capped at 4096.
- query.py: opus-4-8 and fable-5 added to the adaptive-thinking and
  effort allowlists, fable to the thinking-eligible pattern — without
  this, opus-4-8 would take the budget_tokens path, a hard 400 on 4.8+.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: octo-patch <266937838+octo-patch@users.noreply.github.com>
…ase (#712)

Replicate the experiment behind RTK's README savings table
(https://github.com/rtk-ai/rtk estimates -80% over a modeled 30-minute
session) with real measurements, and showcase the results in the READMEs.

eval/eco/ benchmark:
- capture_corpus.py (stdlib-only) captures 27 real operations: failing +
  green pytest/go/jest runs from sample projects with genuine bugs (RTK's
  "never synthetic" fixture rule), npm/pip installs, git clone/status/
  commit/push --progress, repo-scale log/diff/ls/find/cat/grep, a ~35k-line
  macOS system log, and small must-pass-through rows. Marker-file guard
  refuses to rmtree a --workdir it didn't create; missing toolchains skip;
  partial failure exits non-zero.
- measure.py replays each capture through the production path (baseline =
  _assemble_bash_body(truncate_output(...)); compress_bash_output; the
  mapper's returnCodeInterpretation suffix on both sides) and counts
  tiktoken cl100k_base tokens of the exact wire content. Tee-filename
  entropy (time_ns/pid/counter) is pinned and the tee dir fixed, so reruns
  on an unchanged corpus are byte-identical. Never-worse asserted
  corpus-wide. Percentages floor so display never overstates savings.
- Committed results: corpus total 92,989 -> 17,767 tokens (-80%), filter
  hits -88%, and an honestly conservative recompute of RTK's own session
  model at -19% (eco compresses results only; small outputs pass through).

READMEs: new /eco section (before/after pytest example, measured table,
RTK-model comparison in <details>, safety guarantees, reproduce commands)
in README.md and README_ZH.md, compact translated sections in AR/FR/HI/PT/
RU, /eco rows in the REPL command tables, eval/README.md pointer.

src/eco: npm >=9 lowercased its log prefixes — noise pattern now matches
"npm warn" alongside WARN/notice (npm error still always survives); found
via the real npm-install capture. New regression test pins both casings.

Critic-reviewed: two REVISE rounds (determinism + rmtree guard majors,
wire-exactness and display-floor nits) then APPROVE with independent
re-execution of the capture and byte-identical measure verification.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…713)

Replace the DeepSeek Prefix Cache banner with the measured /eco story
(same session, 80% fewer Bash-output tokens; 92,989 -> 17,767 across 27
real operations), with an explicit #eco-benchmark anchor down to the full
benchmark section. The DeepSeek economics remain in the 2026-06-18 news
item, which the banner and the /eco section now point at.

Add a 2026-07-13 news item (#708 + #712) to README.md, docs/NEWS.md, and
README_ZH.md; both README news lists stay at the 10 most recent (the
GLM-5.2 item rolls off — already archived in docs/NEWS.md).

Wording is engine-exact per critic review: failure summaries keep the
error lines that matter (kept lines never rewritten), every LOSSY
compression tees the full output to disk (safe-loss ceremony strips do
not claim recoverability), and the RTK-model recompute names its -19%.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
PR #713 swapped the DeepSeek Prefix Cache banner out for the new /eco
Token Compression one. Bring both back: /eco stays as the newer banner,
the original DeepSeek banner (restored byte-for-byte from before it was
ever touched, git show 22c4be0^:README.md) follows immediately after.

Repoints the two DeepSeek-prefix-cache cross-references that had been
redirected at the News section back to a direct #-deepseek-prefix-cache
anchor now that the heading exists again. Softens "now THE headline" to
"now A headline" in the 2026-07-13 news item (README, docs/NEWS.md,
README_ZH) since there are two banners now, not an exclusive one.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Changelog](https://github.com/openai/openai-python/blob/main/CHANGELOG.md)
- [Commits](openai/openai-python@v1.0.0...v1.109.1)

---
updated-dependencies:
- dependency-name: openai
  dependency-version: 1.109.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…52 (#716)

Updates the requirements on [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) to permit the latest version.
- [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases)
- [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/main/CHANGELOG)
- [Commits](prompt-toolkit/python-prompt-toolkit@3.0.0...3.0.52)

---
updated-dependencies:
- dependency-name: prompt-toolkit
  dependency-version: 3.0.52
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(release): prepare PyPI publishing

* test(bridge): avoid shutdown grace delay
* feat(eval): Harbor agent adapter for terminal-bench 2.0

Adds eval/harbor/clawcodex_agent.py, a Harbor (harbor-framework)
installed-agent that bootstraps uv + clawcodex-cli from PyPI inside each
task container and runs it headless (--print --output-format stream-json
--dangerously-skip-permissions, IS_SANDBOX=1 for the root safety gate).
Model names use Harbor's provider/model convention and are split into
--provider/--model. Token totals are backfilled from the final
stream-json result event.

Smoke-verified on terminal-bench@2.0 fix-git + openssl-selfsigned-cert
with deepseek/deepseek-v4-flash: 2/2 reward 1.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(eval): docker credsStore fix is the durable path, DOCKER_CONFIG the fallback

The credential-helper hang is best fixed by switching config.json to the
osxkeychain helper (verified on this machine, auths empty so nothing to
migrate); keep the DOCKER_CONFIG=~/.docker-nocreds recipe as the
non-invasive alternative.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(eval): critic follow-ups for the harbor adapter

- Scope API-key forwarding to the provider under eval (unknown/absent
  provider falls back to the full allowlist; --agent-env unaffected)
- Test for an actual CA bundle instead of [ -d /etc/ssl ]
- Drop unused bash from the apk sieve; document CLAWCODEX_MAX_TURNS
- Note the unquoted build_cli_flags() splice hazard for future flags
- README: harbor view has no trajectory pane without ATIF

Critic verdict on the branch: APPROVE (these are its suggestions applied).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2.1 (harbor-framework/terminal-bench-2-1) is the verified iteration of
2.0 (89 tasks, 26 fixed for bugs/timeouts/reward-hacking robustness). It
resolves from Harbor Hub as `terminal-bench/terminal-bench-2-1` — no
@Version — and hub datasets namespace task names, so include filters
need the full `terminal-bench/<task>` form (or a glob).

Smoke-verified with the existing adapter + deepseek/deepseek-v4-flash:
4 trials, 0 infra errors; fix-git -k 2 scored pass@2=1.0 (mean 0.5).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…tion mode (#722)

* feat(query): wire --effort end-to-end; harbor adapter subscription mode

Effort wiring (port of TS effort.ts / main.tsx:995):
- `clawcodex --effort {low,medium,high,max}` on the print path, threaded
  HeadlessOptions -> run_query_as_agent_loop -> QueryParams.
- QueryParams.thinking_effort becomes None=auto; resolve_thinking_effort()
  resolves explicit > settings.effort > "medium" at the wire boundary, so
  the persisted /effort setting is now live on all three QueryParams sites
  (the documented-inert pipeline in effort_command.py is gone).
- "max" clamps to "high" off the max-effort allowlist (TS
  modelSupportsMaxEffort: opus-4-6 only) instead of 400ing.
- Capability matrix extended with opus-4-8/fable-5 rows + 11 new tests.

Harbor adapter (eval/harbor):
- `--ak subscription=true`: authenticate the Anthropic provider with a
  Claude Pro/Max subscription — host-side refresh of
  ~/.clawcodex/anthropic-oauth.json (5-min slack, locked, atomic save),
  injected per-trial via exec env into a config dir OUTSIDE the synced
  /logs tree; ANTHROPIC_API_KEY deliberately not forwarded (it would win
  over OAuth inside clawcodex). Sessions/transcripts still sync back,
  minus the token file.
- `--ak effort=...` -> clawcodex --effort (CLAWCODEX_EFFORT fallback).
- `--ak source=git+...` installs clawcodex from a git spec instead of
  PyPI (evaluating unreleased code); adds git to the container package
  sieve; mutually exclusive with version=.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(effort): full Claude ladder low|medium|high|xhigh|max, wire-probed gating

The Claude effort ladder includes xhigh (between high and max). Wire
probes on the first-party API (2026-07-18, subscription auth):

  opus-4-8:   xhigh OK, max OK
  sonnet-4-6: xhigh -> 400 ("Supported levels: high, low, max, medium"),
              max OK
  opus-4-6:   xhigh -> 400 (same message), max OK (also documented)

So the vendored TS snapshot's modelSupportsMaxEffort (max = opus-4-6
only) is stale: max is broadly accepted and it's xhigh that needs
gating. Replaced with _model_supports_xhigh_effort (opus-4-8, fable-5
by analogy); resolve_thinking_effort degrades xhigh->high off-allowlist
and passes max through. --effort/settings/VALID_EFFORT_VALUES/ /effort
picker+help all accept the 5-level ladder; adapter CliFlag likewise.

Tests updated to the probed semantics (127 passing across effort/
settings/query/cli/compat files), including flipping the deliberate
"xhigh is invalid in Python" divergence tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: critic round — parity auto-effort, token containment, subagent seed

src (effort):
- Auto path now OMITS output_config entirely (TS configureEffortParams
  parity: undefined -> no param; the API applies its model default,
  "high" per TS docs) instead of forcing clawcodex's historical
  hardcoded "medium". Tests updated; ladder-sync test added.
- xhigh added to frontmatter EFFORT_LEVELS; stale 4-level comments and
  "inert pipeline" notes corrected; --effort help documents main-loop
  scope (subagents follow settings.effort).

adapter (critic blockers/majors):
- BLOCKER: /logs/agent is a live bind mount of the host trial dir —
  copy-back switched from copy-all-then-delete to an ALLOWLIST
  (sessions/transcripts/todos), so the OAuth token never transits /logs.
- Containers now receive a refresh-token-free credential copy (empty
  refresh_token; host-side refresh raised to 30-min runway, above any
  agent timeout) — no rotation fan-out hazard.
- Token write hardened against symlink planting in the 1777 config dir
  (rm -f + set -C noclobber); refresh POST moved off the event loop
  (asyncio.to_thread).
- subscription=true now rejects --ae ANTHROPIC_API_KEY (harbor injects
  agent-env into every exec, bypassing _build_env; the key would outrank
  OAuth and silently bill the API).
- _oauth_file honors CLAWCODEX_OAUTH_FILE > $CLAWCODEX_CONFIG_DIR > ~.
- effort kwarg additionally seeds settings.effort in the container's
  global config so SUBAGENTS inherit the level (--effort alone is
  main-loop only).
- README/docstrings updated to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(eval): include projects/ in the copy-back allowlist

Headless runs write per-project session transcripts under
<config-dir>/projects (CC-style layout), not sessions/ — the allowlist
missed it, so trial dirs synced no session artifacts. Token exclusion
unchanged (config-dir root still never copied).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: critic re-review leftovers — skills-loader xhigh + warning grammar

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The _headless_args SimpleNamespace simulates argparse output;
_run_print_mode now reads args.effort (PR #722), so the fixture needs
the attribute like every other flag it mirrors. Fixes the only 2 CI
failures (8609 passed otherwise).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
)

Runs the vendored TypeScript Claude Code implementation (typescript/
dist/cli.mjs, openclaude 0.13.0) through the same harbor harness as
clawcodex, for apples-to-apples terminal-bench scoring:

- Uploads the host-built single-file bundle (bun run build) per
  container + npm-installs its 7 unbundled runtime externals (pinned to
  the working host tree; native sharp/ripgrep need platform-correct
  binaries, and 3 further externals are lazy per-provider imports that
  never load on the Anthropic path).
- Node >= 22 bootstrap: system node if suitable, apk on alpine, else
  the official nodejs.org tarball.
- Claude-subscription auth is ENV-ONLY: the shared host-side refresh
  (clawcodex_agent.fresh_subscription_credentials) feeds
  CLAUDE_CODE_OAUTH_TOKEN — the fork treats it as an inference-only
  token (no refresh, never persisted; write-paths audited), so the
  config dir can live under the synced /logs tree for free session-log
  syncback. CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 turns on the fork's own
  Bash-subprocess secret scrub so a prompt-injecting task can't `env`
  the token into transcripts (critic catch).
- --provider anthropic always pinned: the any-LLM fork otherwise
  auto-routes to detected credentials (observed live: a ChatGPT/Codex
  profile 400ing an opus request).
- --max-turns 300 (flag exists but is hidden from --help — critic
  catch) and --effort low|medium|high|max mirror the clawcodex adapter
  for comparability.

Live-verified on terminal-bench 2.1 with anthropic/claude-opus-4-8 +
subscription + effort=high: 3/3 trials reward 1.0, 0 errors; token/cost
metrics populated. Critic verdict: REQUEST CHANGES -> fixes applied.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ns (#725)

Adds eval/harbor/claude_code_subscription.py — a thin subclass of
Harbor's own claude-code agent (bootstrap-installs the latest official
CLI in-container; pin with --ak version=) that injects a per-trial
refreshed Claude-subscription access token (shared 30-min-runway host
helper) via CLAUDE_CODE_OAUTH_TOKEN, with CLAUDE_FORCE_OAUTH so a stray
ANTHROPIC_API_KEY can never bill the API.

Subprocess-env scrub: modern claude-code implements
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 via bubblewrap and HARD-FAILS
without bwrap (observed live — it also crashed the bootstrap when
sprayed across install via the Trial extra_env overlay, which Trial
snapshots BEFORE run()). Shipped as an ENV_VARS descriptor that reaches
the run exec only, defaulted OFF ("0") to match stock harbor/
leaderboard posture; opt in with --ak subprocess_env_scrub=true on
bubblewrap-equipped images. NOTE: the vendored openclaude fork's same
var is plain env filtering (=1 correct there) — do not harmonize.

Live e2e on terminal-bench 2.1 (anthropic/claude-opus-4-8 +
reasoning_effort=high): 3 passing trials reward 1.0, 0 errors;
agent_info auto-detected claude-code 2.1.215; ATIF/cost/token
accounting inherited from the parent.

Critic: REQUEST CHANGES (scrub-snapshot channel) -> ENV_VARS redesign +
bwrap discovery -> APPROVE.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The vendored typescript/ tree moved 0.13.0 -> 0.24.0. Adapter deltas:

- Runtime externals repinned to the 0.24.0 host tree — now the full
  13-package COMMON_EXTERNALS set (adds @aws-sdk/client-bedrock,
  client-sts, credential-provider-node, @smithy/core,
  @smithy/node-http-handler, @azure/identity; bumps aws-sdk/google-auth
  versions). At 0.13.0 three of these were absent lazy imports.
- --effort choices gain xhigh (0.24.0 ladder; "ultracode" deliberately
  excluded — workflow mode, not a reasoning level).
- Docstrings: the opus-4-8 conservative-128k caveat is FIXED in 0.24.0
  (proper 1M-context/128K-output metadata registered), which also
  removes the compaction handicap the 0.13.0 comparison runs carried.

Live-verified on terminal-bench 2.1 (opus-4-8 + subscription +
effort=high): 2/2 trials reward 1.0, 0 errors, agent version
auto-detected 0.24.0, the 128k metadata warning gone from both trial
streams, host preflight PONG green. Design unchanged from the
twice-critic-APPROVED adapter; this is a mechanical pin/enum refresh.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…#727)

Port of typescript/vscode-extension/openclaude-vscode to clawcodex at
vscode-extension/clawcodex-vscode: Control Center webview (runtime /
provider / project-settings status, project-aware terminal launch), chat
sidebar + editor panel (streaming text and thinking deltas, tool cards,
per-turn usage), session history with resume, theme, and activity-bar
icon.

The chat backend is `clawcodex agent-server --stdio` (the same backend
the Ink TUI spawns) rather than headless -p stream-json, because headless
auto-denies permission asks and lacks the control_request protocol and
per-turn results. Interactive permission prompts work end to end:
can_use_tool cards with warning / session_label / plan payloads,
allow-session persisted via chosen_updates, deny honored. Client-side
RPCs (resume, list_sessions) correlate control_response replies by
request_id; abort is the interrupt control (SIGINT would kill the
session). Sessions are read from ~/.clawcodex/sessions with a
realpath-normalized workspace filter (unbounded scan, output-capped —
an input-side cap can silently empty Resume). The Azure/Foundry env
subsystem of the reference is replaced with clawcodex-native provider
detection from ~/.clawcodex/config.json; every divergence is documented
in PORT_NOTES.md.

Backend fix shipped with it: an agent-server `interrupt` during a live
Anthropic stream stopped the deltas but never emitted result/cancelled —
response.close() from the abort listener cannot wake a reader parked in
ssl.read(), so the worker hung (reproduced on the installed build too;
even the stream watchdog's own timeout close was ineffective).
force_close_response() now shuts the underlying socket down
(SHUT_RDWR via the httpcore network_stream extension) before closing,
used by both the StreamAbortGuard and the watchdog. Regression tests
include a real-socketpair blocked-recv unblock.

Tests: 84 node --test cases behind a local vscode stub plus a live e2e
script (scripts/e2e-agent-server.mjs, 8 checks incl. permission
round-trip, interrupt -> cancelled, and post-interrupt turn); Python
abort/watchdog/server/provider suites green.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Follow-up to #727: remove every openclaude mention from the shipped
extension. PORT_NOTES.md becomes BACKEND_NOTES.md, rewritten as
self-contained backend/wire-contract documentation (agent-server
protocol, RPC correlation, permission persistence, session store,
provider integration, streaming derivation, stderr policy) with the
same technical content and no provenance framing. README pointer and
package.json files list updated; two source comments reworded to stand
alone. Also gitignore packaged *.vsix artifacts.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…de to non-streaming

Root cause of 18/89 NonZeroAgentExitCodeError trials in the 2026-07-19
terminal-bench run (mean 0.584 vs claude-code 0.719): the stream idle
watchdog (90s, TS-parity default) recovered by re-issuing the request
NON-streaming via chat() — which the Anthropic Python SDK refuses
outright for opus-class max_tokens ("Streaming is required for
operations that may take longer than 10 minutes"). Every watchdog fire
on an agentic call was therefore fatal: exit 1, trial zeroed, including
runs 13 minutes in. The referenced TS recovery
(streamLatencyWatchdog.ts:resumeViaChatCompletion) does not exist in
the vendored tree — real Claude Code aborts the timed-out stream and
RETRIES THE STREAM (claude.ts abortTimedOutStream + retry re-issue),
and had zero such crashes on the same tasks. The #730 1M-context fix
made large prompts (and >90s first-event latencies) common enough to
surface this latent landmine.

- chat_stream_response: bounded re-stream loop (stream_idle_max_attempts,
  default 3 total attempts, env CLAUDE_STREAM_IDLE_MAX_RETRIES); on
  exhaustion raise StreamIdleTimeout with harness-classifiable
  "Connection timed out" phrasing. Attempt body extracted to
  _stream_attempt (returns None on watchdog fire). _fallback_to_chat
  deleted. Same x-client-request-id across attempts.
- chat(): explicit per-request timeout (API_TIMEOUT_MS env, ms, default
  600s — TS getApiTimeoutMs parity), which also disarms the SDK's
  >10-min guard for the legitimate non-streaming callers (compaction
  summarize, session title, judges).
- stream_watchdog: stream_idle_max_attempts helper; stale
  "falls back to non-streaming" docs corrected.
- tests: retry-then-succeed, exhaustion-raises (chat() never called),
  retry-budget env override, chat timeout default/env/caller-override.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The retry loop (prior commit) removed the fatal non-streaming downgrade
but the 18 terminal-bench trials STILL failed: a live re-run turned the
crash into StreamIdleTimeout after 3x90s. Real root cause, traced to the
SDK: in the agentic loop every turn re-sends the growing conversation, so
every stream call pays PROMPT-PROCESSING time-to-first-event, which scales
toward minutes as context approaches opus-4-8's 1M window (the #730 fix
made this common). The Anthropic Python SDK DROPS the ping keepalives the
API sends during that window (_streaming.py: `if sse.event == "ping":
continue`), so the watchdog — which iterates the TYPED event stream — sees
dead air during a healthy-but-processing request and fires. Retrying
re-incurs the same TTFT. The crash duration_ms clustered at 94-100s (just
past the flat 90s window) is the fingerprint. claude-code (0.719, same
tasks) never crashes because the TS watchdog survives this window.

Fix: two-phase deadline in StreamWatchdog. The FIRST event gets a longer
grace (stream_first_event_timeout_seconds, CLAUDE_STREAM_FIRST_EVENT_
TIMEOUT_MS, default 300s — above realistic 1M-context TTFT, below the
600s non-streaming request ceiling that still catches a truly dead
socket); reset() flips to the 90s inter-event idle once events flow.
_effective_timeout_locked drives both the deadline and half-time-warning
timers off the current phase. Complements the retry loop (transient
mid-stream stalls) and the explicit chat() timeout.

Tests: two-phase grace/fire/dead-socket coverage + env resolution;
pre-existing short-timeout unit tests pass first_event_timeout_s so they
still exercise first-fire. 82 passing across watchdog/provider/ch04.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two-phase grace (prior commit) fixed only the tasks whose stall was
the initial time-to-first-event (e.g. regex-log: crash -> reward 1.0). A
live re-run showed the rest still failing: their >90s dead-air windows
occur AFTER message_start (so the 90s inter-event deadline governs, not
the 300s first-event grace) — the model does heavy extended-thinking /
internal work mid-stream while the API sends only ping keepalives, which
the Anthropic Python SDK drops from the typed stream. duration_ms ~282s =
3 attempts x ~94s = the 90s inter-event firing three times.

Real fix, matching the ping-aware TS watchdog: the dropped pings are
still BYTES on the wire that httpx.Response.num_bytes_downloaded counts.
On each deadline the watchdog now checks byte progress and RE-ARMS
instead of firing when bytes advanced since the deadline was set — so it
fires only on true dead air (no bytes at all), never on a healthy stream
the SDK simply isn't surfacing typed events for. Zero changes to the
delicate accumulation path (the counter is read off stream.response).
The two-phase grace stays as the time-based fallback when the counter is
unavailable (mocked/non-httpx transport).

Tests: byte-progress-prevents-fire + no-progress-still-fires (real
timers, MagicMock byte counter). 84 passing across watchdog/provider/ch04.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sition test

- Document that first_event_timeout_s resolves independently of timeout_s
  (a caller passing only timeout_s still gets the 300s env/default grace).
- Widen the phase-transition test's first-event grace 400ms->800ms so a
  CI scheduling stall can't flip the "must not fire during grace" assert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…h paths (critic nit)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ericleepi314

Copy link
Copy Markdown
Collaborator Author

Superseded by a clean-based branch (main history was rewritten under this branch, making its merge-base stale). Reopening as an identical change from current main.

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.

6 participants