openclaw/docs
Dallin Romney 373a207231
chore: prepare extended-stable 2026.8.35 (#163214)
* fix: preserve plugin settings through update recovery (#160344)

Keep incompatible local plugin configuration intact and complete deferred migration confirmation after unchanged package convergence. Use completion receipts to clear obsolete retry warnings without rewriting update history.

Fixes #159477. Thanks @EndeavorPioneer for the recovery report.

(cherry picked from commit 8e66b0b6c8)

* fix: prevent duplicate Gateways during container onboarding (#160193)

* fix: prevent duplicate Gateways during container onboarding

* fix: keep QA cron controls scoped to the child

Drop inherited parent cron suppression while preserving explicit child runtime
settings. Cover the failing inheritance contract at the environment owner.

Await node-list RPC responses after acknowledged rename and reconnect events
without racing the existing RPC deadline against a separate polling timeout.

(cherry picked from commit 54b1b1b63e)

* fix: deeply nested MCP tool results crash result projection (#160825)

Handle deeply nested MCP structuredContent as an actionable tool error instead of letting a serialization RangeError escape result projection. Preserve server content and report the same failure to Code Mode guest callers, without retaining the unprojectable value for downstream serialization.

Preserves ordinary shallow results and adds focused regression coverage.

Co-authored-by: wangmiao0668000666 <wang.miao86@xydigit.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
(cherry picked from commit 452c808d5b)

* feat(openai): support GPT-6.1 Sol (#161400)

* feat(openai): support GPT-6.1 Sol

Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* feat(openai): support GPT-6.1 Sol

Worked on by:
- @steipete

Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
OpenClaw-Publication: 970e2aab-1efa-4534-be78-7b6ec08717b5

* fix(openai): preserve GPT-6.1 Sol request capabilities

Preserve mandatory reasoning through subscription discovery and configured Responses requests. Extract existing catalog readers and discovery coverage to respect file-size ratchets.

Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

* test(openai): narrow discovered model request fixture

Require the discovered row before converting catalog modalities and optional context metadata to the typed chat runtime model.

Co-authored-by: steipete <58493+steipete@users.noreply.github.com>

---------

Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
(cherry picked from commit 858993c1a4)

* fix(logging): redaction stalls for seconds on long plus-joined text (#161089)

* fix(logging): stop redaction stalling on long plus-joined text

The seven vendor-token patterns built on BASE64_SAFE_TOKEN_BOUNDARY ran
their data-URL negative lookbehind at every `+`, `/`, or `=` boundary of a
base64-class run, rescanning the whole run each time. On two-byte subjects
(any character above U+00FF) V8 no longer rejects those boundaries early,
so 100 KB of `+`-joined text took seconds; JSC shows it on more shapes.

Check the token with a lookahead first so the lookbehind runs only where a
complete token starts. The matched language and capture groups are
unchanged.

* refactor(logging): inline single-use redaction helpers

Compact the base64-safe token helper (identical pattern strings) and fold the naming-only readEnvAssignmentKey wrapper into its only caller, keeping the fix production-LOC negative.

(cherry picked from commit ff21cc82ce)

* fix(gmail): recover watcher after transient restart bind conflicts (#161503)

The Gmail watcher stayed down for good when a Gateway restart briefly found its port still in use (EADDRINUSE). It now retries the bind a few times with bounded backoff and recovers once the port frees. If the port stays taken, it reports a clear error and stops retrying.

Fixes #161467. Reported by @kazuyuki-eguchi.

Proof: a real isolated Gateway, with the Gmail watcher on local fakes.
- Before the fix, the watcher stayed down after a transient port conflict. After it, the watcher recovered and answered.
- With the port held for the whole run, the watcher stopped after the initial attempt plus three retries, with a clear error.
- The regression test fails before the fix and passes after, and 31 focused tests pass.
- Updating from the published 2026.9.6 build to this build succeeded in two fresh runs (102 s and 97 s), and the installed build passed both the transient and the persistent control. The first updater run failed at service activation and didn't recur. The watcher can't reach that step: rehearsal disables hooks, and this diff doesn't touch activation or lease code.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit 8095304914)

* fix(sessions): reject missing and malformed session targets (#161533)

* fix(sessions): reject compaction of missing sessions

Return an actionable INVALID_REQUEST instead of a successful no-op for missing targets in both compaction modes. Preserve explicit no-op outcomes for existing empty sessions.

* fix(sessions): reject malformed reset agent selectors

Use strict session agent input validation before lifecycle cleanup so an invalid explicit selector cannot reset the default agent. Extract reset-target resolution from the oversized lifecycle service while retaining selected-global targeting.

Co-authored-by: Peter Steinberger <steipete@gmail.com>
(cherry picked from commit 670c2d0d6a)

* fix(agents): honor captured subagent announce skips (#161542)

Authoritative terminal snapshots bypassed ANNOUNCE_SKIP filtering and woke
the requester unnecessarily. Apply the existing completion selector before
expanding retained history, then suppress the successful announce while
preserving child cleanup. Keep global terminal snapshots, legacy fallback
behavior, and failed or timed-out child outcomes unchanged.

The real Gateway reproduction made three parent model requests instead of
two; five fixed repetitions avoid the extra turn. Three pre-fix regression
failures now pass. Blacksmith Testbox proof: 634 related unit cases, 87
announce E2E cases, 15 stub-provider operator scenarios repeated five times,
and the full changed-code gate. Streaming/reconnect/timeout-compaction
baseline flows passed once; their five-repeat rerun and process-restart
proof were not completed within the campaign time box. No live credentials.

Changed-test wall costs (pnpm test <file> --maxWorkers=1):
T2 COST FILE src/agents/subagents/announce/subagent-announce.test.ts
T2 TEST WALL 47.63 seconds
T2 COST FILE src/agents/subagents/announce/subagent-announce-output.test.ts
T2 TEST WALL 43.91 seconds
T2 COST FILE src/agents/subagents/announce/subagent-announce-result.test.ts
T2 TEST WALL 29.28 seconds
T2 COST FILE src/agents/subagents/completion/subagent-completion-result.test.ts
T2 TEST WALL 1.33 seconds

Co-authored-by: Peter Steinberger <steipete@gmail.com>
(cherry picked from commit 806f961301)

* fix(auto-reply): release canceled thinking-catalog waits (#161319)

* fix(auto-reply): release canceled thinking-catalog waits

Race the canceled reply's model-level and status waits against its own
AbortSignal without canceling shared catalog discovery.

Refs #161310

* fix(ci): keep database-worker test routes unique

The duplicate pdf-tool.resources.test.ts entry introduced in #161270
causes preflight to fail while splitting core-runtime-infra-storage-state.
Retain its original route once so PR test plans can be built.

* fix(auto-reply): keep abort assertion outside preprocessing import cycle

Move the existing AbortError assertion into a small reply leaf module so
model-level resolution can use it without importing the preprocessing graph.
The reply assertion behavior remains unchanged.

* test(ui): wait for shell viewport sync before splash geometry check

* fix(auto-reply): cancel earlier directive catalog waits

* chore: keep catalog cancellation PR scoped to reply handling

* test(auto-reply): avoid returning timer handle from executor

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit f18202c76b)

* fix(tts): pass the responding agent to summary model acquisition (#161454)

* fix(tts): pass the responding agent to summary model acquisition

summarizeText acquired its model without an agent id, so it fell back to the default agent and threw the no-explicit-owner error in multi-agent configs. Forward the reply's agent id from maybeApplyTtsToPayloadCore. The injected-deps path is unchanged.

Fixes #161431

* test(tts): cover multi-agent summary through real model selection

Drives summarizeText for agent 'work' in a two-agent config through the
real acquisition and selection path, stubbing only the completion call.
Asserts the ownership error is gone and the summary keeps using the
documented agents.defaults.model.primary fallback.

* test(tts): retain behavioral owner regression and document auth scope

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit 5073ee41ab)

* fix(imap): bound backlog message retention during sweeps (#160413)

The IMAP watcher collected every unseen message into memory before processing any of them, so a large backlog held every message body at once. Sweeps now fetch and process unseen messages in bounded batches, in UID order, with the existing cursor, retry and sender-gate behavior unchanged.

Fixes #160353. Thanks @Ayushdevo for the fix and @addyCooks for the report.

Proof: a real ImapFlow TCP connection to an in-process IMAP server holding 2,000 messages, run in a container.
- On main, the sweep held all 2,000 bodies at once. With this change it held at most 20, and every message was admitted exactly once in UID order.
- A rejection injected at UID 25 resumed from cursor 24.
- A sender-gated message (UID 30) was skipped once.
- The regression test fails before the fix and passes after, and the watcher suite passes 21/21.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit ecbe1861ef)

* fix(cron): isolated setup times out while the thinking catalog hydrates (#161132)

Isolated cron turns spent their whole 60-second setup budget waiting for the native thinking catalog to load, so the setup watchdog killed runs whose catalog was slow (measured at 13–65 s on 2026.9.6). The cron path now bounds that wait the same way `loadFullModelCatalog` already does and falls back to the published catalog facts. The shared `loadNativeModelCatalog` stays unbounded for the other callers that depend on it returning the complete catalog.

Fixes #161112. Thanks @jayzhou2309 for the fix and @Flakedict for the report and the measurements.

Proof: a real isolated Gateway in a container, with the mock provider's native catalog publication held open.
- On main, cron setup hit the watchdog at 60,012 ms. With this change, the cron turn replied in 11,756 ms while the publication was still held.
- When the catalog is fast, the turn replied in 1,683 ms using the full catalog.
- The regression test fails before the fix and passes after, and the scoped tests pass 7/7.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit 786ed6b566)

* fix(matrix): preserve direct mappings when account-data reads fail (#161727)

Preserve existing Matrix peer mappings when an account-data read fails. Keep best-effort fallback on read-only inspection and propagate read errors before writes.

The regression failed on the original code and passes after the fix. Direct-room, sibling sender/FIFO, changed-file and zero-cycle checks passed; repair documentation explains the failure behavior.

(cherry picked from commit 78d75e9350)

* fix(agents): detached runs keep their transcript when a settled turn is finalized (#161091)

* fix(agents): preserve detached finalization history without internal prompts

Keep the host-owned session manager and persistence mode during settled-turn finalization. Refresh one-shot user persistence suppression when reusing the guard so empty-answer retries do not retain internal recovery prompts. Preserve completed tool receipts and subsequent genuine user turns.

Regression fails before the guard repair with two retained internal prompts and passes afterward. Focused proof: 126 tests, core tsgo, scoped lint, formatting, and whitespace checks pass. Independent Codex review found no actionable P0-P2 findings. Both native-live cases remain enabled on main.

* test(agents): reuse a caller-owned admission without growing the attempt runner

* test(agents): pass the caller-owned admission from the typed override

(cherry picked from commit 8f924bff5d)

* fix(worker): stop durable session retries from freezing node hosts (#160812)

* fix(worker): stop durable session retries from freezing node hosts

* test(worker): name closing window harness as repro

* test(worker): fix loopback harness checks

* test(worker): adapt closing-window regression to Gateway tools

---------

Co-authored-by: Altay <altay@hey.com>
(cherry picked from commit 1de9a42f11)

* fix: preserve plugin install records during legacy updates (#161485)

The v13 wide-row state migration conflated a missing installed_plugin_index row with a row whose JSON was unparseable or shape-drifted, and dropped the table in both cases. An invalid row is now preserved in full in the existing diagnostic_events quarantine with repair guidance recorded, valid install records are retained independently of damaged metadata, and a preservation failure rolls the step back to v12. No schema change.

Closes #161329.

Landed under the pre-existing-red rule: the remaining CI failures were current main reds at this base (Codex app-server settlement fixture drift, fixed on main by 52e60fb42b; the subagent kill-tombstone / descendant-cancellation intermittent first seen on main hourly 36619831492).

(cherry picked from commit e1ea50caf7)

* fix(gateway): restore CLI-backed exec with secret egress proxy (#160760)

* fix(gateway): pass admitted run instance to loopback-mediated exec

* test(gateway): cover egress-enabled MCP exec grants

Prove the real MCP HTTP grant, cached tool construction, egress-enabled process launch, retired-grant rejection, and a later run in the same session. Route the fixture through the existing native database-worker test group.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* test(ui): wait for transcript resize before measuring centering

The real layout owner publishes viewport width asynchronously after resize. Wait for rendered width readiness, then sample all centers atomically while retaining visible-box and one-pixel assertions. The focused catalog E2E now passes in secretless AWS; the real-browser mechanism reproduces the prior 160px phase and still rejects a 2px displacement.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* fix(ci): restore Doctor plugin repair lint budget (#160715)

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* fix(ci): restore config and node adapter lint budgets (#160843)

* fix(ci): keep config env and worker tests within lint budgets

* test(gateway): share node capacity and rejection fixtures

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* refactor(talk): combine transcript early-return guards

Preserve echo-first short-circuit evaluation while restoring the existing relay file-length budget. No runtime behavior or lint limit changes.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* refactor(ui): share model setup activation payloads

Reuse the same discovery-to-activation projection for prepared and directly selected candidates, retaining optional-field omission and excluding discovery metadata. Restore the existing model setup page line budget without changing UI behavior or lint limits.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* test(gateway): join chat execution before metadata assertions

The operator.write verbose-level fixture asserted agent.wait success after a one-second observation window without joining its detached run. Reuse the existing execution observer before the same scoped RPC, preserving the timeout and every metadata/permission assertion.

Verified the complete chat file on CI's Node 24.19.0 in network-isolated execution: 41 tests passed. Independent P0-P3 review and targeted lint passed.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* test(gateway): join recap producer lifecycle events

Wait for actual model entry before inherited connection drain and for the relocated recap post-commit publication before inspecting persisted state. Preserve model cancellation, old-owner fencing, exact target/store checks and existing persistence assertions. New waits follow test cancellation rather than timing a cold worker through an observation poll.

Verified both edited files (25 tests), the original receiving-merge peer groups (377 and 164 tests), focused lint, both owning type graphs and independent P0-P3 review.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* fix(infra): retain explicit SQLite launch paths when cwd is unavailable

Keep supplied launch and transport facts, resolve relative arguments only against a genuine captured cwd, and anchor native inspections to selected absolute operation paths when ambient cwd is gone. Refuse unresolved cwd-dependent inputs rather than redirecting them. Prove both native transports after real directory removal; cold Node Worker bootstrap remains a separate lifecycle limitation.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* test(gateway): coordinate monitor fault injection with worker admission

Use the existing managed state write transaction for persistent trigger creation and removal. Controlled scheduleUnowned interleaving reproduces both raw-DDL lock failures and passes both managed-DDL variants without changing reload assertions or timeouts. Ensure final trigger cleanup cannot skip service cleanup.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* test(gateway): keep monitor fault injection in fixture support

Preserve the exact managed DDL statements and cleanup lifecycle while shrinking the over-cap reload test. Line-cap, suppression, assertion and import-cycle guards pass; the 115-case file, focused lint, owning types and independent P0-P3 review pass.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* test(update): select host runtime for Homebrew guidance

Keep the Homebrew-specific guidance fixture independent of the container used for isolated proof. Container cases and all guidance assertions remain unchanged.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
(cherry picked from commit 6daee6e4bd)

* fix(sandbox): sessions fail with EACCES after skills refresh on read-only installs (#162304)

* fix(sandbox): keep synced skill copies removable on read-only installs

Sandbox skill sync copied bundled skills with fs.cp, which keeps source
modes. From a read-only install every copied directory became 0555, so the
next full re-sync (skills version bump or Gateway restart) failed with
EACCES unlinking skills/<skill>/SKILL.md, and library-pinned sessions
failed every turn.

Copied directories are now made owner-writable (file modes unchanged), and
removal repairs legacy read-only trees inside the synced child before one
retry. The repair stays inside the skills root and never follows symlinks.
The Claude CLI skills-plugin copy fallback uses the same helper.

* fix(sandbox): classify skill removal errors without a type assertion

(cherry picked from commit be93e85955)

* fix(agents): cron fallback delivers a truncated reply when history caps a long message (#160520)

When a run's final message was longer than the chat history display cap, the run-wait reply reader fell back to the capped history copy and delivered it with an internal `...(truncated)...` marker, cutting off the user's reply. The reader now recovers the full message instead of the display-capped projection. The capped display history keeps its provenance, and text the model writes itself, including a literal marker, is left alone.

Also addresses #82121 at the producer, without global stripping in the sanitizer. Thanks @jayzhou2309.

Proof: real isolated Gateways in a secretless container, with a scripted mock model, over the Gateway WebSocket and the actual run-wait reply reader.
- On main (`f57a952ab0aa`), a 14,329-character report came back as an 8,018-character copy ending in the truncation marker. With this change, it comes back complete and exact.
- Display-cap provenance is kept in history on both builds.
- Normal prose and an intentional standalone marker are unchanged.
- Three recovery cases fail on main and pass here, and the regression suite passes 63 tests.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit ebf4bd70ca)

* fix(update): avoid terminal errors during pnpm updates (#161920)

The updater spawned the package-manager install with inherited terminal stdin but captured stdout/stderr; pnpm 12.1.0's global build-approval prompt then failed with "IO error: not a terminal" and aborted the install. The pnpm package-install step now receives EOF on stdin (pnpm skips the prompt when stdin is not a terminal; no --yes, so consent is unchanged); npm and Bun installs and every other bounded command keep their stdin behavior. docs/install/updating.md gains the first-hop recovery for already-installed updaters.

Closes #161866

Thanks @alkor2000 for the diagnosis and fix.

Co-authored-by: alkor2000 <200923177@qq.com>
(cherry picked from commit bbe0d114e4)

* fix(agents): preserve spawned session cwd at ingress (#162308)

Visible child sessions persist lineage and a managed cwd without an inherited workspace. Keep that cwd for subsequent runs while preserving explicit inherited workspace precedence and sandbox ownership checks. Existing session rows require no migration.

(cherry picked from commit 43d29bbdb1)

* fix: finished subagents keep child slots occupied after delivery expires (#162368)

* fix: release child slots when descendant delivery is suspended

* test: wait for collector completion publication

* ci: install ripgrep for baseline ratchets

* Revert "ci: install ripgrep for baseline ratchets"

This reverts commit 616d5cc3e04e77cf97c5cc6b7a201fb02e966a84.

* Revert "test: wait for collector completion publication"

This reverts commit a9902f4633b12fddccfe4780b3f08ed5ca52cda7.

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit c03ee95e10)

* fix(skills): sandboxed turns fail with EACCES when refreshing read-only skill copies (#162295)

* fix(skills): refresh read-only sandbox skill copies

Repair owner access through confined directory descriptors before removing stale materialized skill trees. Preserve read-only sandbox mounts and source permissions. Cover prior read-only copies, nested cleanup, symlink confinement, and mount identity.

* fix(skills): unlink stale top-level skill symlinks

(cherry picked from commit 81d89fb43b)

* fix: steered message no longer replaces a question whose model request failed (#162439)

Fixes: with the default `steer` queue mode, a message sent while the first message's model request is failing replaces the first message. The chat shows `⚠️ <model> request failed.` and then only the answer to the second message. The first question is never retried or answered.

When a model request fails while a newer message is waiting to steer into that turn, OpenClaw retries or falls back for the first message as usual. The newer message then runs as its own turn. Both questions get answered in order. The failure notice only appears if the first message's retries and fallbacks are all exhausted, the same as when nothing was steered.

Root cause: after a model request failed, `AgentSession.handlePostAgentRun` still continued the session with the queued steer. The embedded runner owns retries for a failed request (`agent-project-settings.ts` turns session auto-retry off, #73781), so the failed message was never retried. The steered message ran in its place, against a transcript where the failed message looked handled. Per-input answer segments (#149925) then correctly reported the first segment's failure, so the user got `⚠️ request failed` plus the second answer.

Provenance:
- `steer` became the default in `4a6e10ece8` ("feat: default queueing to steer") and #77023, to keep the active turn responsive without starting a second run.
- The post-run `hasQueuedMessages() ? "continue"` came from #109709 so that messages queued by `agent_end` handlers are not stranded. That reason still holds after successful turns. This PR only excludes failed requests, where continuing skips the run owner's retry and fallback.
- Steered channel input already waits for its transcript commit (`agent-runner-steer-adoption.ts`, `waitForTranscriptCommit: true`). When the session settles without committing it, the steer is withdrawn from the runtime queue and the parked reservation falls back to the ordinary follow-up queue. That path runs after the reply operation clears. No new queue or retry logic is added.

Fix: one condition. After a failed request, queued input no longer continues the session (`agent-session-prompting.ts`). Docs: `docs/concepts/queue-steering.md`.

Hermes comparison: Hermes injects a steer only at a role-safe boundary after a tool result. With no tool row yet, or when the request fails, it requeues the steer as the next turn's user message, and each queued message gets its own turn. This change gives OpenClaw the same outcome by reusing its existing follow-up fallback.

Relation to #161069 (stalled-turn recovery): that PR covers turns aborted by stuck-session recovery. This one covers a provider failure while a steer is pending. The two don't overlap.

- Fewer model calls: before, a pending steer triggered one extra model call inside the failed run. Now it doesn't.
- The first message keeps its existing retry budget (`MAX_EMPTY_ERROR_RETRIES = 3`, plus the configured fallback chain). Nothing new is retried.
- A handed-back steer is queued at most once. The fallback drops its `steerPending` marker, so it drains as an ordinary follow-up turn and is never steered again. If that turn also fails, it ends with the normal failure notice and is not requeued. The worst case is one follow-up turn per inbound message.
- Tested: the session regression test asserts exactly one model request after the failure, with no continuation. The requeue-once path is existing behavior covered by `src/auto-reply/reply/queue/enqueue.steering.test.ts` (6/6 on this head).

**Telegram Test Server** (`telegram-e2e-userbot`, Convex-leased credential, real QA user in a DM, default queue mode). A deterministic mock provider fails agent-turn requests whose newest question is `17*23` three times (`response.failed` after 8 s). Like the live model in our earlier proof, it answers only the newest user question. The QA user sends `What is 17*23?` at +0.2 s and `Also, what is 19*21?` at +8.2 s, so the second message arrives during the first failing request.

- Before (built head with only this line reverted): the SUT sends `⚠️ openai/gpt-5.5 request failed.` at +13.1 s, then `399` at +14.1 s. The request after the failure carries both questions (`developer,user,user,user`). The first request is never retried, and `391` is never sent.
- After: three failing first-question requests (`[empty-error-retry]` attempts 1/3 to 3/3), none carrying the second question. The fourth request answers `391` at +30.5 s. The second message then runs as its own turn (`developer,user,user,assistant,user,user`) and answers `399` at +31.5 s. No failure notice is sent.

**Unit test:** `agent-session-loop-next-turn.test.ts` "does not answer a steer in place of a failed request". On `main` the steer is committed into the failed turn (`promise resolved instead of rejecting`). With this change there is one request, the steer's commit wait rejects so its caller requeues it, and nothing is left queued. File: 24/24 pass. Related suites pass: `sdk`, `agent-session-loop-correctness`, `attempt-prompt-submit` (+ steering, retention), `attempt-stream-prepare`, `attempt-session-replay` and `provider-review-continuation`, 231 tests.

**Telegram with a live OpenAI model** (same Telegram Test Server DM; a small local proxy in front of the real OpenAI API fails only the first two agent requests that carry 17*23 without 19*21, and forwards everything else to the live model):
- Before (this line reverted in the build): one failing request, then the next request carries both questions and goes to the live model, which answers `399`. The bot sends `⚠️ openai/gpt-6-astra request failed.` (+20.4 s), then `399` (+21.0 s). 17*23 is never answered.
- After: two failing requests for the first question alone, then the live model answers the retry with `391` (+24.1 s). The second message's own turn gets `399` (+26.0 s). No notice.
- Both runs used the fix build, before the branch was rebased onto newer main; the production change is the same. For the before run, only the fix line was reverted.

**Telegram, when the first question fails on every retry and fallback** (same harness; the mock fails every agent request whose newest question is 17*23, and the agent has a fallback model configured): 8 requests for the first question, 4 on `gpt-5.5` and 4 on `gpt-5.5-fallback`. None of them carries the second question. The model-fallback log shows `gpt-5.5` failing over to `gpt-5.5-fallback`, then nothing left to try. The SUT sends exactly two messages: one `⚠️ openai/gpt-5.5-fallback request failed.` at +70.9 s, then `399` at +71.9 s from the second message's own turn. No other notices.

**Web UI `chat.send` (real Gateway, operator WebSocket client of the kind the TUI uses, same mock):** the client sends `What is 17*23?`, then `Also, what is 19*21?` 8 s later, during the first failing request.
- Before (this line reverted in the build): the client's only answer is `399`. The request after the failure carries both questions (`developer,user,user,user`). `chat.history` ends `user 17*23, user 19*21, assistant 399`, so the first question is unanswered.
- After: the steer is withdrawn when the failed attempt settles. Its `chat.send` run closes with an empty final (+17.1 s), and `chat-send-agent-dispatch.ts` dispatches it again; it runs as a follow-up and is not steered into the retry. The first run retries three times, with no retried request carrying the second question, and answers `391` (+33.7 s). The second message's own run answers `399` (+34.0 s). `chat.history`: `user 17*23, assistant 391, user 19*21, assistant 399`.

Why a withdrawn Web UI steer can't be re-steered into the retry: with explicit `queueMode: "steer"`, chat.send's fallback dispatch passes `messageInjectionDisposition: "rejected"` (`src/gateway/server-methods/chat-send-agent-dispatch.ts:388-390`). `runReplyAgent` steers only when the disposition is `"none"` (`src/auto-reply/reply/agent-runner-run.ts:341-346`), so the message is queued as a follow-up instead (`:389`). With the default mode, chat.send makes no injection attempt of its own (`chat-send-admission.ts:272-275`) and the message takes the channel path: its parked reservation falls back with its steer marker removed (`queue/enqueue.ts:307-318`), and the queue drains only after the reply operation clears (`agent-runner-steer-adoption.ts:110-122`).

**Wall times** (`pnpm test <file>`, shared, heavily loaded host):
- `agent-session-loop-next-turn.test.ts`: 24/24, 121 s wall (vitest 106 s). The new test signals request start and steer acceptance with deferreds; it no longer polls.
- `enqueue.steering`: 6/6, 78 s.
- `attempt.queue-message`: 16/16, 98 s.
- `agent-session-handoff-adoption.integration`: 1/1, 63 s.
- `agent-runner-steer-adoption.question-recovery`: 25/25, 97 s.
- `chat-send-steering-custody`: 17/17, 90 s.
- `agent-runner.runreplyagent.e2e`: 223/224 under load. The one failure, "keeps the replacement source when retired admission completes", passes alone (60 s wall). That file mocks the embedded agent, so it never reaches the changed session code.

qa-lab note: the qa-channel serializes inbound per account, so the same-sender steer couldn't be reproduced there. The proof uses the Telegram Test Server instead.

LOC vs `origin/main`: production +3/−1, test +46, docs +1 (changed line).

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit 4d755295bc)

* fix(telegram): restore progress when reply hooks suppress previews (#161546)

* fix(telegram): deliver progress when reply hooks suppress previews

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* fix(telegram): honor global block-streaming opt-out

Preserve the explicit global off preference when reply hooks suppress previews, and cover both hook contracts through Telegram HTTP delivery.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* docs(telegram): clarify hooked fallback for multi-agent turns

Clarify that configured GroupThread turns retain their existing block policy: their previews are disabled independently of reply-modifying hooks. The new forced fallback applies to ordinary single-agent turns. No delivery-policy or test changes.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
(cherry picked from commit 54a6149549)

* fix(webchat): keep saved replies visible during history refresh (#162763)

* fix(webchat): keep saved replies visible during history refresh

Fence pre-commit history snapshots at transcript publication before run settlement. Reuse the existing queued refresh so both active and late background replies survive history races.

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* fix(webchat): keep saved replies visible during history refresh

Worked on by:
- @VACInc

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
OpenClaw-Publication: 18964aa1-fac1-42b9-8777-682b1d10ef09

---------

Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
(cherry picked from commit e42c52b7d8)

* fix(cron): stale automatic tool lists block scheduled jobs from tools their owner has (#162432)

Related: #130753, #137832, #147969

Fixes: some scheduled jobs created by an agent fail for months because a tool list saved by an older OpenClaw build is missing tools the creator actually had, such as the native shell. In our setup, a monthly group job that runs `node <script>` delivered nothing in August, delivered nothing in September (the run still reported `ok`), and posted a blocker in October. Its saved list had 31 tools and no `exec`.

User impact: an agent-created agent-turn job that does not name specific tools now gets the same tools as its owner conversation at run time, like a job an operator creates without `--tools`. Existing jobs with an automatically saved creator snapshot behave the same way from their next run. Nothing stored is rewritten: no migration and no backups. Explicit tool lists, script payloads, condition triggers, and jobs bound to captured Codex app authority keep their stored list.

Tradeoff, approved by the maintainer (Ayaan): a per-sender tool policy on the creating owner, or a plugin hook that narrowed the creating turn, no longer limits these default jobs. Only owners can create automations from chat, and subagents cannot create them.

**History of the saved list.**
- #91499 introduced it so a delayed run cannot do more than its creator could.
- #112483 made every agent-created job store one, because runs have no sender.
- #112661 made scheduled runs re-apply the owner session's group policy and every non-sender limit, keeping the stored list as the upper bound.
- #137832 fixed native tool capture for new jobs only, and deliberately did not widen stored lists.
- #147969 added a Doctor advisory. It only fires for claude-cli, so it never covered Codex-harness or built-in OpenAI jobs like ours.

**Root cause.** When no tool list was given, OpenClaw saved a frozen copy of the creating turn's tools instead of treating the job like an operator `*` job. Every capture bug (missing native tools, late configured MCP, renamed tools) then stayed in the job permanently.

**Fix.** This follows Hermes, which keeps no creator snapshot: `cron/scheduler.py` `_resolve_cron_enabled_toolsets` reads toolsets from config at run time.
- **New jobs.** An agent-turn create or update with no list, or `*`, stores `["*"]`. That is the same value operator jobs store, so the job's tools match a normal turn in its owner conversation. Script payloads and condition triggers still store the creator's concrete tools, because a script reaches MCP only through servers its list names. Jobs whose creator captured Codex app authority also keep the concrete list, because that authority is bound to it.
- **Existing jobs.** One helper, `resolveCronRunToolsAllow` in `src/cron/tools-allow.ts`: a stored automatic snapshot (`toolsAllowIsDefault`) runs as `*` when it has a valid scheduled owner policy, no condition trigger, and no Codex app authority. Otherwise it keeps its stored list. Every execution consumer of the stored list uses it: the run payload, the command-prompt preflight, and the scheduled message authority.
- **Script transitions.** A `*` job that becomes a script, or gains a condition trigger, captures the creator's concrete tools.
- **Exec pin.** A `*` list keeps the creator's exec host pin.
- **No new noise:** automatic snapshots stay excluded from the `web_search` provider warning, as on main.
- **Deleted, now pointless:** both Doctor advisories about incomplete automatic snapshots, the run warning about pre-MCP snapshots, and two exports nothing uses anymore.

Review note: on claude-cli, a `*` job runs without a CLI tool cap, so Claude's native tools behave exactly as in a normal chat turn in that conversation. This PR introduces no new path around `tools.deny` that a chat turn doesn't already have.

Live-model Telegram proof (Telegram Test Server DM, leased team credential, live `openai/gpt-6-astra` reached through a forwarding proxy that stands in for the runner's mock provider; the runner harness itself is unchanged). This reproduces the shape of the original incident:
- The tester DMs the bot, which creates the owner conversation.
- A job owned by that conversation is added. Its stored list is an old-style automatic snapshot `["automations","message","read"]` plus `toolsAllowIsDefault: true`, with no `exec`.
- The payload is `Run: node scripts/split-report.mjs and post its output line verbatim`. The workspace script prints a random nonce.
- The job is run once (`cron run --wait`), with announce delivery to the DM.

| Build | `exec` offered | Model action | What arrived in the DM | Run |
|---|---|---|---|---|
| base 94f5a8d (main before this PR) | no | `tool_search` ×2, then gave up | "Could not run node scripts/split-report.mjs: no command-execution tool is available…" | error |
| **this PR, head 5b78cb7** | **yes** | `exec {"command":"node scripts/split-report.mjs"}` | "**SPLIT-REPORT 93C53909**: general 41, design 17, ops 9" (the exact script output, with this run's random nonce) | ok, delivered |
| head 5b78cb7 with `tools.deny: ["exec"]` | no | `tool_search`, `read`, then gave up | "Could not run node scripts/split-report.mjs: no command-execution tool or paired node is available…" | error |

In every run, the stored job kept `["automations","message","read"]` plus the marker. Before and after use the same scenario and driver; only the checkout differs.

Update and live proof: published `openclaw@2026.9.7`, then this branch at the exact head (2f5099d), on the same state directory. Mock provider. Every process ran under a temporary `HOME` and state directory. Each job's message makes the model call `exec` with `touch <effects>/<job>`.

1. 2026.9.7 created both jobs through `cron.add` (scheduled policy `trusted`). With the Gateway stopped, the "stale" job was given the old automatic-snapshot shape `["automations","message","read"]` plus `toolsAllowIsDefault: true`. sha256 of both stored rows: `609358837…`.
2. Runs:

| Build / config | Job | `exec` offered | Side effect | Run |
|---|---|---|---|---|
| 2026.9.7 | stale automatic snapshot | no | absent | error |
| 2026.9.7 | explicit `["read","message"]` | no | absent | error |
| this branch | stale automatic snapshot | **yes** | **created** | ok |
| this branch | explicit `["read","message"]` | no | absent | error |
| this branch, owner policy narrowed to `tools.deny: ["exec"]` | stale automatic snapshot | no | **absent** | error |
| this branch, `tools.deny: ["exec"]` | explicit `["read","message"]` | no | absent | error |

3. After the branch runs, the stored rows were byte-identical (same sha256 `609358837…`), and job ids and lists were unchanged. Nothing was migrated.

An earlier run at e151ea3, with the same harness, also covered a snapshot bound to Codex app authority: `exec` was not offered, the file stayed absent, and the stored row was unchanged.

Tests:
- `run.tools-allow.test.ts`: a stored automatic snapshot `["message","read"]` reaches the embedded run as `["*"]`, with the owner's scheduled policy intact. It fails on main with `["message","read"]`.
- `cron-tool-creator-cap.test.ts`: a default agent turn stores `["*"]`, while a trigger script and a Codex-app creator keep the concrete snapshot.
- `run.tools-allow.test.ts`: snapshots without a valid owner policy, or behind a condition trigger, keep their list. Both cases fail on the previous head.
- `run.tools-allow.test.ts`: no `web_search` warning for an automatic snapshot that kept its list. This fails without the exclusion.
- `run.message-tool-policy.test.ts`: a self-edited automatic snapshot runs on CLI with no cap.
- `run.tools-allow.test.ts`: a legacy `Command to run:` prompt from an automatic snapshot without shell tools now runs instead of being rejected.
- `jobs-tool-policy.test.ts`: scheduled message authority is admitted for an automatic snapshot that lacked `message`.
- `cron-tool-creator-cap.test.ts`: a `*` agent turn converted to a script captures the creator's concrete tools.
- These three regressions fail on the previous head. `node scripts/check-changed.mjs` passes.
- Explicit-list, exec-pin and gateway creator-transport suites pass. `pnpm tsgo:core` passes.

No new path triggers a model call or a job run. The change only selects which tool list an already scheduled run uses.

LOC vs main: production +98/-250 (net -152), tests +140/-373, docs +19/-11.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit 08498ec40d)

* fix(cron): deliver manual runs started from an agent turn after that turn ends (#162780)

Fixes: when an agent turn starts an automation with `automations` `run` (for example the owner says "run my report now"), the run fails with `attempt disposed before transcript write` once that turn ends. For a `sessionTarget: "current"` job the report is never delivered. For other jobs the report is still sent, but its copy in the conversation transcript is lost (a WARN).

User impact: "Run it now" from a chat delivers the report, the same as a scheduled run. No new settings, messages or warnings.

Root cause: the `automations` tool calls `cron.run` through the in-process gateway, so `enqueueRun` runs inside the calling turn's AsyncLocalStorage, including its owned transcript-write context (`withOwnedSessionTranscriptWrites`). The queued run inherits that context. When the run writes into the caller's session (the current-session report or the delivery mirror), `runWithOwnedSessionTranscriptWrite` matches the session and routes the write through the caller's attempt lifecycle. By then that turn has ended and its lifecycle is disposed, so the write is rejected.

Provenance:
- #104595 (`b6e95201f8`, "retain detached manual run admission") made the queued manual run its own gateway root (`runWithGatewayIndependentRootWorkContinuation(..., "cron:manual-run")`), so it no longer depends on the caller's request. That helper resets only gateway work admission, not other per-turn context.
- Later, #115404 (`d16e33e08e`) and #121113 (`ce53f7e82e`) carried the turn's transcript lifecycle and writer fence in an AsyncLocalStorage, so nested writes stay serialized and fenced to the running attempt. #115404 also added `runWithoutOwnedSessionTranscriptWrites` for work that outlives its turn. Subagent completion (`b3d3f860e4a`), `sessions_send` follow-ups, media-generation completions, and session event wakes all use it. The manual-run detach point from #104595 never got it.

Fix: `enqueueRun` starts the queued run outside the caller's owned transcript context (`runWithoutOwnedSessionTranscriptWrites` around the existing independent root continuation). It reuses the helper the sibling detached paths use; no new mechanism. The invariants of those PRs still hold: the run is still a tracked independent gateway root (#104595); the caller's activation and commit guards still run before acceptance, and they don't touch transcripts; writes made during the turn are still fenced to its attempt (#115404/#121113). The queued run gets its own lifecycle when its attempt starts, the same as a scheduled run.

Hermes: `trigger_job` doesn't run the job inside the caller. It marks the job for the scheduler's next tick, so a manual run always starts from scheduler-owned context. This change does the same for OpenClaw's queued manual run, at the point where it detaches.

- **Live, Telegram Test Server DM, live OpenAI model** (`telegram-e2e-userbot`, Convex-leased credential, fresh isolated gateway built from each ref, ports 19951/19952, provider slot = pass-through proxy to the OpenAI API; the tester is the configured owner, `session.dmScope: "main"`). The owner creates `current-check` (`sessionTarget: "current"`, `payload: agentTurn "Reply with exactly CURRENT-REPORT"`, `delivery: announce`, disabled), then sends: "Use the automations tool to run the automation named current-check now (action run, runMode force …). When the run call returns, reply with exactly RUN-DONE".
  - Command (secrets omitted): `E2E_MOCK_SERVER_PATH=<live proxy> node .agents/skills/telegram-e2e-userbot/scripts/run-mock-sut-user-e2e.mjs --gateway-port 19951 --mock-port 19952 --dm --timeout-ms 420000 --scenario <scenario> --record events.ndjson --output summary.json` → exit 0 on both refs. The scenario waits 90 s after the last reply.
  - **Before, `origin/main` `26bcc94353c`:** DM shows `SETUP-DONE` (20.8 s) and `RUN-DONE` (87.7 s), then nothing more. The run receipt is `error: attempt disposed before transcript write`. The gateway logs it at the cron run session (`outcome=error`).
  - **After, this branch:** DM shows `SETUP-DONE` (24.2 s), `RUN-DONE` (94.4 s), **`CURRENT-REPORT` (96.4 s)**. The run receipt is `ok`, and the gateway log has no `attempt disposed` lines. The WARN/ERROR lines are the same harness startup lines as in the before run.
- **Regression test** (`src/cron/service/ops.regression.test.ts`, "runs a manual run queued from an agent turn outside that turn's transcript lifecycle"): `enqueueRun` is called inside a real attempt transcript lifecycle (`createEmbeddedAttemptTranscriptLifecycle` + `withOwnedSessionTranscriptWrites`). The calling turn disposes, then the run writes into the caller session through the real `runWithOwnedSessionTranscriptWrite`. On `origin/main` the run finishes `error`; with this change it finishes `ok` and the write lands.
- `pnpm exec vitest run src/cron/service/ops.regression.test.ts src/cron/service/ops.run-admission-cleanup.test.ts src/cron/service/ops.run-admission.test.ts src/cron/service/manual-ack-durability.test.ts` → 4 files, 46 tests passed.
- `node scripts/check-changed.mjs` on `997394f3a3a` → exit 0 (format, lint, core and test typecheck, line-cap and dead-export ratchets).
- Test cost: `pnpm test src/cron/service/ops.regression.test.ts --maxWorkers=1` → 15 tests passed, 33.9 s wall (vitest 30.7 s, mostly transform; the new test itself takes about 40 ms).
- Size vs `origin/main`: production +6/−2 ignoring whitespace (+75/−71 raw, because the existing callback is re-indented one level); test +58.

Not changed here: in both runs the `current` run waits behind the caller turn, and the tool's run wait returns "Not finished yet" after about 60 s before the turn replies. That wait is a separate issue.

One AsyncLocalStorage `exit` per queued manual run. No new state, timers, retries or queues. The run's own transcript writes go through its own attempt lifecycle, as for any scheduled run.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit 510beb8d52)

* fix(codex): prevent Gateway heap exhaustion with large agent fleets (#162912)

Closes #162802

Fixes Gateway heap exhaustion during Codex session discovery on large agent fleets. Thanks to @609NFT for the report and allocation profiles.

Large fleets can finish startup and retain their native Codex session catalog without retaining a whole fleet configuration for every agent/home pair. No schema, stored-data, configuration, or catalog-output changes are required.

The existing config-identity cache now owns one captured configuration per generation. Agent/home entries keep their separate directories and cloned connection options, but share that captured configuration. Config reload isolation and source-specific backoff remain unchanged.

No overlap with Pash/Sarah changes.

Codex source inspection: `codex-rs/app-server/src/request_processors/thread_processor.rs:2524–2608` in the sibling Codex checkout confirms that `thread/list` consumes pagination and filter parameters; the fleet configuration capture being repaired belongs to OpenClaw, not the native listing protocol.

Compared baseline `7ef388bac8` with candidate `f0ae922f60922f2b357bc3d5af22a2142799fbe0` in isolated Linux arm64 Docker containers, Node 24.21.0. The synthetic profile contained 739 agents, eight explicit Codex homes, and three native sessions (380,439 bytes of configuration).

| Check | Baseline | Candidate |
| --- | --- | --- |
| Real Gateway, 2,560 MiB heap | Heap OOM at 100.3 s | Survived the six-minute window; clean shutdown |
| Sampled catalog allocations under `resolveRequestOptions` → `structuredClone` | 2.248 GB | 5.48 MB |
| Post-startup heap, 150–360 s | Process already terminated | Bounded at 377–566 MiB |
| Complete startup, separate 6,144 MiB control without profiling | 98.34 s | 93.83 s |
| HTTP listening, same startup control | 50.52 s | 49.56 s |

The larger-heap startup control lets the baseline complete startup rather than comparing a successful candidate against an OOM.

- Real `sessions.catalog.list` Gateway RPCs returned exactly the three expected native session IDs for agents `agent000`, `agent001`, and `agent738`.
- Exact-head live gate: a real OpenAI `gpt-5.4-mini` turn through the candidate Gateway's Codex harness returned `CATALOG_LIVE_OK` on the 739-agent profile (5.449 s). Its temporary read-only credential file and container were removed.
- Regression failed on baseline: four fleet config clones instead of one across multiple agents/homes. It passes on the candidate and also checks reload allocation isolation; the regression itself took 5 ms.
- All 38 focused tests passed across `session-catalog-listing-cache.test.ts`, `session-catalog-request-lifetime.test.ts`, `session-catalog-homes.test.ts`, and `session-catalog-backoff.test.ts` (38.67 s wall).
- Standalone `pnpm test extensions/codex/src/session-catalog-listing-cache.test.ts --maxWorkers=1` passed (32.61 s wall, including runner preparation).
- Built and exercised the real candidate runtime with `pnpm build`. Broader static and project-wide validation is left to CI.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit fd0124688d)

* fix(agents): preserve complete CLI subagent answers (#162843)

Fixes #162777.

The claude-cli transcript writer now records `__openclaw.runId` on the terminal assistant row, matching the field other writers set and the completion reader expects. CLI-backed subagents now deliver their complete final answer instead of the truncated-by-retention fallback.

Proof: real isolated Gateway with a fake claude-cli backend. main delivered the fallback and dropped the tail of a 14,025-character child answer; with this change the parent received all 400 lines including the tail marker. A failed CLI child still reports its error with no fabricated final. Live control: one real OpenAI embedded subagent turn delivered its exact answer. Regression tests fail before and pass after.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit d7cea26df2)

* fix(reef): admit gpt-6.1-sol as a documented immutable guard model (#162955)

OpenAI's gpt-6.1 generation publishes no dated snapshots either, so the Reef
guard rejected the Team configuration that pinned gpt-6.1-sol and the channel
could not start. Admit the exact id beside the gpt-5.6 ids, with the same
documented residual risk; bare family aliases stay rejected.

(cherry picked from commit d77cdd2af6)

* fix: remote MCP plugins fail to start in native agent sessions (#162376)

* fix: remote MCP plugins fail to start in native agent sessions

* test: align remote MCP assertions with normalized transports

(cherry picked from commit 4587ef903c)

* fix(llama-cpp): managed server fails on clean Windows installs (#163093)

* fix(llama-cpp): ship Windows VC runtime app-local

* fix(llama-cpp): make Windows runtime staging fallback-only

(cherry picked from commit 45762faaa8)

* fix(secrets): keep store entry kind when rotating a value (#158968) (#160926)

* fix(secrets): keep store entry kind when rotating a value

`secrets store set <NAME> --value-file <path>` resolved the entry kind
from the name heuristic whenever no host-policy flag was passed, so
rotating the value of an entry created with `--kind secret` under a name
the heuristic does not classify as sensitive silently converted it into a
readable `env` entry and deleted its `--allow-host` allowlist. `import`
classified every entry from its name for the same reason.

Both paths now inherit the stored kind, and that inheritance is resolved
inside the authoritative store write transaction rather than captured
before the CLI awaits its value or its confirmation. A protection change
committed while one of those waits is pending is therefore no longer
overwritten by the pending write, and the value it carries lands under
the newer policy instead of reaching agent subprocesses as plaintext.

Because a kind resolved at write time can invalidate a value the CLI
already accepted, `import` now hands its whole batch to a single
store-owned write. `writeSecretStoreEntries` resolves every live kind and
validates every resulting entry inside one transaction before it writes
any of them, so an entry that a concurrent protection change turns into
an empty secret still fails the command with nothing committed, matching
the existing no-write-on-validation-failure contract.

An explicit `--kind` still overrides both the stored kind and the name
heuristic in either direction.

(cherry picked from commit 66a8c01c8db76bf63ab79c506880f462856b2d1e)

* fix(secrets): satisfy CI pools, formatters, and assertion ratchet

- Run the kind-inheritance purge through the expiry kernel directly so it
  does not require the host-broker state worker (the CLI project executes
  test files off the main thread).
- Replace uncommented kind casts with runtime narrowing for the assertion
  SAFETY ratchet; reformat to oxfmt.

Co-authored-by: yetval <yetvald@gmail.com>

* chore(ci): rerun checks

* ci: retrigger checks for flaky shard reruns

* ci: retrigger checks (flaky infra shards)

* test(ci): dedupe pdf-tool.resources in database worker core paths

670e3fb4ea (#161270) re-added src/agents/tools/pdf-tool.resources.test.ts
to databaseWorkerCoreTestFiles, which already listed it (16b17b22eb). The
changed-node shard planner now rejects repeated files in a split timing
generation, so preflight threw 'split timing generation repeats files for
core-runtime-infra-storage-state' for every PR touching any non-README path.

* fix(secrets): enforce literal input safety at commit

Carry argv provenance to the store transaction and refuse literal values when
kind inheritance resolves to a secret. Preserve explicit env reclassification,
cover concurrent protection, and clarify the set/import kind rules.

Strengthen rotation-value, committed-kind output, and redacted batch no-write
regressions. Existing Gateway writer paths remain unchanged.

Co-authored-by: zachisfine <131436334+zachisfine@users.noreply.github.com>
Co-authored-by: yetval <yetvald@gmail.com>

---------

Co-authored-by: yetval <yetvald@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: zachisfine <131436334+zachisfine@users.noreply.github.com>
(cherry picked from commit 0ae13a5620)

* chore(release): prepare 2026.8.35

* fix(release): adapt backports for 2026.8

* docs(changelog): refresh 2026.8.35 ledger

* fix: repair extended-stable backport integration

* fix: preserve cleanup failures on 2026.8

* test: complete worker cleanup before gateway close

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: wangmiao0668000666 <wang.miao86@xydigit.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: RoboClaw <services+roboclaw@openclaw.org>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Baumus <126391633+Baumus@users.noreply.github.com>
Co-authored-by: Y.B. <paranoyouz@gmail.com>
Co-authored-by: Ayush Tiwari <147244828+Ayushdevo@users.noreply.github.com>
Co-authored-by: Jay Zhou <zhoujunbai123@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: RileyJJY <0668000974@xydigit.com>
Co-authored-by: Altay <altay@hey.com>
Co-authored-by: Vito Cappello <hixvac@gmail.com>
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: alkor2000 <131229172+alkor2000@users.noreply.github.com>
Co-authored-by: alkor2000 <200923177@qq.com>
Co-authored-by: Sam Armstrong <armstrongsam25@gmail.com>
Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Jony <619963502@qq.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
Co-authored-by: zachisfine <131436334+zachisfine@users.noreply.github.com>
Co-authored-by: yetval <yetvald@gmail.com>
2026-10-01 23:34:30 -07:00
..
.generated chore(release): prepare 2026.8.2 candidate 2026-08-31 19:11:04 -07:00
.i18n chore: prepare extended-stable 2026.8.34 (#160960) 2026-09-29 12:04:32 -07:00
announcements feat(plugins): externalize iMessage channel (#117101) 2026-08-01 08:01:18 +08:00
assets
automation chore: prepare extended-stable 2026.8.34 (#160960) 2026-09-29 12:04:32 -07:00
channels chore: prepare extended-stable 2026.8.35 (#163214) 2026-10-01 23:34:30 -07:00
clawhub feat(clawhub): show security audit before installs (#131233) 2026-08-27 16:20:59 -07:00
cli chore: prepare extended-stable 2026.8.35 (#163214) 2026-10-01 23:34:30 -07:00
concepts chore: prepare extended-stable 2026.8.35 (#163214) 2026-10-01 23:34:30 -07:00
diagnostics fix(gateway): reduce control-plane stalls during concurrent turns (#133683) 2026-08-30 18:56:13 -07:00
gateway chore: prepare extended-stable 2026.8.35 (#163214) 2026-10-01 23:34:30 -07:00
help chore: prepare extended-stable 2026.8.34 (#160960) 2026-09-29 12:04:32 -07:00
images
install fix(update): keep plugin Doctor warnings from blocking upgrades (#162065) 2026-09-30 13:59:34 -07:00
maturity docs: compact generated maturity scorecard rows (#130331) 2026-08-26 14:32:19 -07:00
nodes fix: retain 2026.8.1 release fixes on main (#134045) 2026-08-31 10:38:47 -07:00
platforms fix: publish Linux bundles from canonical release branches (#134359) 2026-08-31 12:17:17 -07:00
plugins chore: prepare extended-stable 2026.8.35 (#163214) 2026-10-01 23:34:30 -07:00
providers fix(anthropic): preserve cache prefixes across runtime context (#157650) 2026-09-25 00:24:17 -07:00
reference chore: prepare extended-stable 2026.8.34 (#160960) 2026-09-29 12:04:32 -07:00
releases docs: clarify mistaken 2026.9.1 beta publication (#134512) 2026-08-31 16:50:00 -06:00
security refactor(imap): consume the core identifier-authentication scale (#131178) 2026-08-27 14:19:39 -07:00
snippets/plugin-publish chore(deps): refresh eligible seven-day npm dependencies (#133772) 2026-08-31 16:48:58 -07:00
specs fix(codex): support paginated message forks with Codex 0.151.0 (#132908) 2026-08-29 21:58:45 -07:00
start fix(setup): restore runtime capability review (#134101) 2026-08-31 14:59:45 -07:00
tools chore: prepare extended-stable 2026.8.34 (#160960) 2026-09-29 12:04:32 -07:00
web fix(ui): simplify chat selection action (#134596) 2026-08-31 18:09:29 -07:00
agent-runtime-architecture.md
AGENTS.md docs: retire redundant guides and fix gateway package instructions (#133722) 2026-08-30 21:10:43 -07:00
auth-credential-semantics.md fix(auth): keep explicit-root operations isolated through activation and rollback (#131519) 2026-08-27 21:57:38 -07:00
ci.md ci: cache native dependency outputs for hosted jobs (#150725) (#155387) 2026-09-21 20:33:08 -07:00
CLAUDE.md
date-time.md
docs.json docs: retire redundant guides and fix gateway package instructions (#133722) 2026-08-30 21:10:43 -07:00
docs_map.md docs: generate docs map at publish time (#117398) 2026-08-01 07:01:43 -07:00
index.md docs: reframe docs for teams and soften group-chat guidance (#132012) 2026-08-28 11:47:17 -07:00
logging.md chore: prepare extended-stable 2026.8.33 (#151452) 2026-09-19 12:45:09 -07:00
nav-tabs-underline.js
network.md docs: retire redundant guides and fix gateway package instructions (#133722) 2026-08-30 21:10:43 -07:00
openclaw-agent-runtime.md
prose.md refactor(plugins)!: remove OpenProse (#128494) 2026-08-24 10:39:46 +05:30
style.css
vps.md docs: align systemd host tuning with managed policy (#128752) 2026-08-24 08:38:27 -07:00
whatsapp-openclaw.jpg