- Fix latest_session_alias_resolves_most_recent_managed_session test:
the test created sessions with 0 messages, which are now filtered out
by the message_count > 0 check in latest_session_excluding(). Updated
the test to call push_user_text() before saving so sessions have
at least one message and are findable by /resume latest.
- Add distinct error message when all sessions are empty (0 messages).
Previously, the same "no managed sessions found" message was returned
whether there were zero sessions or all sessions had 0 messages. Now:
- No sessions at all → "no managed sessions found in {path}. Start
claw to create a session..."
- Sessions exist but all empty → "all sessions are empty (0 messages)
in {path}. This usually means a fresh claw session is running but
no messages have been sent yet. Wait for a response in your other
session, then try --resume latest again."
- Add test for the all-sessions-empty error path.
Addresses reviewer feedback on #3216.
- Add missing retry_after: None field to ApiError::Api construction
in main.rs test. This field was introduced by the Retry-After
header support but was not added to the test's error initializer,
causing a compile error under CI's strict mode.
- Remove duplicate #[must_use] attribute on retry_after() method
in error.rs (lines 134+138 both had it; kept the outer one
above the doc comment per convention).
- Cargo fmt --all run.
- Reviewer question "Are defaults preserved?" — answered yes:
ApiTimeoutConfig defaults to 30s connect / 300s request / 8 retries.
with_retry_policy() is opt-in. No behavior change without explicit
configuration.
Three improvements to the /resume command:
1. /resume latest now skips the current empty session
When a new session is created on startup (with 0 messages), /resume
latest previously returned that empty session. Now it skips sessions
with message_count == 0 and excludes the current session ID via the
new exclude_id parameter, so it finds the previous session with
actual conversation history.
2. Unified load_session_excluding() replaces load_session_loose()
The previous load_session_loose() only handled cross-workspace
resume for aliases. The new load_session_excluding() combines the
loose workspace validation logic with the exclude_id parameter,
simplifying the call chain and ensuring all resume paths skip the
current empty session when appropriate.
3. All existing session scanning paths (global root + project-local
.claw/sessions/) are already in place from prior commits, and now
the exclude_id filter is applied consistently across both local
and global session scans.
Changes:
- session_control.rs: Add resolve_reference_excluding() that delegates
from resolve_reference(), adding optional exclude_id filtering for
alias references.
- session_control.rs: Add latest_session_excluding() that delegates
from latest_session(), filtering out excluded session IDs and
sessions with 0 messages in both local and global scan paths.
- session_control.rs: Add load_session_excluding() that replaces
load_session_loose(), combining cross-workspace alias handling with
the exclude_id parameter.
- main.rs: Add load_session_reference_excluding() that delegates from
load_session_reference(), using the new store method.
- main.rs: Wire LiveCli::resume_session() to pass the current session
ID as the exclude_id so /resume latest skips the current empty
session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cherry-picked from PR #2816 onto current upstream/main, resolving
conflicts from PR #3015's merge (which added retry_after to ApiError
but some construction sites were missing it).
Commits preserved:
- ade85398: API timeout config, Retry-After header, configurable retry
- TimeoutConfig in HTTP client builder (connect 30s, request 5min)
- CLAW_API_CONNECT_TIMEOUT and CLAW_API_REQUEST_TIMEOUT env vars
- Retry-After header parsing on 429 responses
- ApiTimeoutConfig in runtime config (settings.json)
- 8a883430: retry 400 responses with transient gateway error bodies
- Detects known gateway phrases in 400 response bodies
- Marks them as retryable instead of hard-failing
- ed91a61e: add 'no parseable body' to CONTEXT_WINDOW_ERROR_MARKERS
- Some providers return 400 with 'no parseable body' for oversized
requests instead of a proper context_length_exceeded error
Commits skipped (already in upstream via PR #3015):
- 453ab642: optional id field (already merged)
- baa8d1ba: HTML detection in streaming (already merged)
- 33d2f789: JSON error detection in streaming (already merged)
8 files changed, 299 insertions, 80 deletions
Some OpenAI-compat backends (e.g. glm-5.1-fast) return 400 with
"no parseable body" when the request payload is too large to parse,
rather than a proper context_length_exceeded error. Without this marker,
is_context_window_error() returns false and the auto-compact retry
loop never triggers — the user just sees an opaque 400 error.
💘 Generated with Crush
Assisted-by: GLM 5.1 FP8 via Crush <crush@charm.land>
Some providers/proxies return HTTP 400 with bodies like "no parseable
body" or "connection reset" during transient network blips. These are
not real bad requests — they're gateway errors wearing a 400 mask.
Detect known gateway error phrases in 400 response bodies and mark
them as retryable so the existing exponential backoff handles them.
- Add TimeoutConfig to HTTP client builder with connect_timeout (30s)
and request_timeout (5min) defaults, configurable via
CLAW_API_CONNECT_TIMEOUT and CLAW_API_REQUEST_TIMEOUT env vars
- Add with_timeout() builder to both AnthropicClient and
OpenAiCompatClient for per-client timeout configuration
- Parse Retry-After header on 429 responses and use it to override
exponential backoff delay when present
- Add ApiTimeoutConfig to runtime config with apiTimeout settings
in ~/.claw/settings.json (connectTimeout, requestTimeout, maxRetries)
- Add retry_after field to ApiError::Api for propagating rate limit
backoff hints through the retry pipeline
Close two ways the permission system could be bypassed:
- Workspace path traversal: normalize `.`/`..` lexically before the
boundary prefix comparison so paths like `/workspace/../../etc` can no
longer escape the sandbox. Fixed in both the runtime enforcer and the
duplicate check in the tools PowerShell path classifier.
- read-only mode no longer trusts the leading token alone: reject shell
metacharacters (chaining/substitution/redirect/pipe/subshell), drop
interpreters and build drivers (python/node/ruby/cargo/rustc) from the
allow-list, gate `git` to non-mutating subcommands, and reject `find`
actions that execute or delete.
Adds regression tests for both holes. The pre-existing, unrelated
worker_boot git-metadata test failure is not affected by this change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Commands like /commit, /pr, /issue, /bughunter, /ultraplan are
interactive-only and NOT resume-safe. Previously the generic
interactive_only error always suggested 'claw --resume SESSION.jsonl
/commit', which would just re-trigger interactive_only.
Fix: check commands::resume_supported_slash_commands() in the
SlashCommand::Ok(Some(cmd)) arm. Resume-safe commands get the full
--resume suggestion; non-resume-safe commands only say 'Start claw'.
Also update two existing unit tests whose assertions checked for the old
'interactive-only' substring (now 'interactive_only:' prefix).
Two new integration tests:
- non_resume_safe_interactive_only_hint_omits_resume_suggestion
- resume_safe_interactive_only_hint_includes_resume_suggestion
572 tests pass, 1 pre-existing worker_boot failure unrelated.
/approve, /yes, /deny, /no (and /y, /n) are valid REPL-only slash
commands. Outside the REPL they were falling through to
format_unknown_direct_slash_command -> error_kind:unknown_slash_command.
Fix: intercept them in the SlashCommand::Unknown arm and emit
interactive_only: prefix so classify_error_kind returns the correct kind.
One new test: approve_deny_outside_repl_emits_interactive_only (covers
/approve, /yes, /deny, /no)
572 tests pass, 1 pre-existing worker_boot failure unrelated.