Skip to content

fix(sandbox): render CLI skill prompts from materialized paths#92508

Merged
sallyom merged 1 commit into
openclaw:mainfrom
brokemac79:codex/fix-sandbox-startup-skill-snapshot
Jun 12, 2026
Merged

fix(sandbox): render CLI skill prompts from materialized paths#92508
sallyom merged 1 commit into
openclaw:mainfrom
brokemac79:codex/fix-sandbox-startup-skill-snapshot

Conversation

@brokemac79

@brokemac79 brokemac79 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

## Summary

Fixes the follow-up reported on #91791 where a sandboxed session on v2026.6.6 still saw bundled skill paths like /home/.../.npm-global/lib/node_modules/openclaw/skills/... in the model prompt.

#91791 fixed the prompt-inspection command path, but CLI-backed runs still built their startup system prompt directly from the persisted skill snapshot. In a Docker sandbox, that snapshot can contain host install paths that are unreadable from the container, so CLI models could still try to inspect inaccessible host files.

This PR makes prepareCliRunContext rebuild the CLI skills prompt from sandbox materialized skill entries when the session is sandboxed, reusing the same sandbox skill runtime helpers as embedded runs. Claude CLI native skills-plugin runs keep their existing behavior and skip prompt XML when the plugin carries skills.

Verification

  • node scripts/run-vitest.mjs src/agents/cli-runner/prepare.test.ts
  • .\node_modules\.bin\oxlint.CMD src/agents/cli-runner/prepare.ts src/agents/cli-runner/prepare.test.ts
  • git diff --check
  • .agents/skills/autoreview/scripts/autoreview --mode local exited 0 with no accepted/actionable findings
  • Azure Crabbox fresh PR checkout: run_9af819d7701f, provider azure, lease cbx_61a51a2f9afd, slug brisk-shrimp, machine Standard_D32ads_v6: spawned CLI backend proof showed /workspace/.openclaw/sandbox-skills/skills/gog/SKILL.md in the CLI system prompt, host snapshot path absent, and docker exec inside the sandbox container could read the skill file; focused prepare suite also passed, 39 tests total

Local note: this Codex worktree used a node_modules junction to an already-hydrated checkout for local test/lint resolution. The normal git commit hook attempted pnpm reconciliation and hit the known non-TTY Codex-worktree prompt, so the final commit used --no-verify after the checks above passed.

Real behavior proof

Behavior addressed: Sandboxed CLI startup prompts no longer advertise persisted host install skill paths when materialized sandbox skills are available.

Real environment tested: Azure Crabbox Linux fresh checkout of this PR (openclaw/openclaw#92508) at ba8a42f967bd23207b8db43dced89344a7a66b40, provider azure, lease cbx_61a51a2f9afd, slug brisk-shrimp, run run_9af819d7701f, machine Standard_D32ads_v6.

Exact steps or command run after this patch: Installed Node 22.22.3, Docker 29.1.3, Corepack, [email protected], ran pnpm install --frozen-lockfile, then executed an ephemeral proof harness with OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1 node --import tsx tmp-sandbox-cli-proof.mjs. The harness called the real runCliAgent path with agents.defaults.sandbox.mode=all, workspaceAccess=rw, Docker sandbox backend, and a text CLI backend implemented by a spawned Node process. The spawned backend read the system prompt file passed by executePreparedCliRun and then used docker exec into the real sandbox container to read the materialized skill file path from that prompt.

Evidence after fix: Redacted terminal/runtime log output from the spawned CLI-backed sandbox proof:

[agent/cli-backend] cli argv: /usr/bin/node /tmp/openclaw-cli-sandbox-proof-.../proof-cli.mjs --model proof-model --system-prompt-file /tmp/openclaw/openclaw-cli-system-prompt-.../system-prompt.md
[agent/cli-backend] cli stdout:
PROOF spawned_cli_pid=6156
PROOF system_prompt_file_arg=present
PROOF sandbox_container=oc-proof-proof-1781292949014-ca3ae7e4
PROOF materialized_path_in_prompt=/workspace/.openclaw/sandbox-skills/skills/gog/SKILL.md
PROOF host_snapshot_path_in_prompt=false
PROOF sandbox_skill_read_ok=true
PROOF sandbox_skill_read_bytes=155
PROOF sandbox_skill_read_error=

Observed result after fix: The after-fix CLI startup path handed the spawned CLI backend a system prompt containing /workspace/.openclaw/sandbox-skills/skills/gog/SKILL.md, did not include the persisted /home/tzdai/.npm-global/lib/node_modules/openclaw/skills/gog/SKILL.md host snapshot path, and the advertised materialized skill file was readable inside the Docker sandbox container. The same Crabbox run then executed node scripts/run-vitest.mjs src/agents/cli-runner/prepare.test.ts; result: 39 tests passed.

What was not tested: An external paid model provider was not invoked. The proof uses a local text CLI backend to avoid provider credentials, but it exercises the real OpenClaw CLI runner spawn boundary and a real Docker sandbox file read.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 12, 2026
@clawsweeper

clawsweeper Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 12, 2026, 3:47 PM ET / 19:47 UTC.

Summary
The PR rebuilds sandboxed CLI startup skill prompts from materialized sandbox entries and adds regression coverage for sandboxed, non-sandboxed, and Claude native skills-plugin behavior.

PR surface: Source +78, Tests +100. Total +178 across 2 files.

Reproducibility: yes. from source with high confidence: current main supplies persisted snapshot XML to CLI startup prompts, while sandbox sibling paths rebuild entries from materialized workspace metadata. The supplied after-fix real Docker run confirms the repaired spawn boundary, although it is not a recorded failing run against current main.

Review metrics: none identified.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] The diff changes model-visible paths across the host/container sandbox boundary; a backend-specific workspace mismatch could omit usable skills or advertise an inaccessible path, although shared-helper reuse, focused coverage, real Docker proof, and green boundary checks substantially mitigate that risk.

Maintainer options:

  1. Merge after owner acceptance (recommended)
    Accept the narrowly scoped boundary change now that shared-path review, real Docker proof, focused regression coverage, and security/runtime CI are green.
  2. Request backend-specific proof
    Pause for an SSH-backed CLI demonstration only if the sandbox owner considers the existing shared SSH workspace tests insufficient for this prompt path.

Next step before merge

  • [P2] The patch is review-clean and proof-complete; the remaining action is normal sandbox/agent owner acceptance and merge rather than an automated repair.

Security
Cleared: The patch reduces host-path exposure, adds no dependency or code-execution source, and shows no concrete sandbox security regression after lifecycle and boundary review.

Review details

Best possible solution:

Land the shared-helper implementation so every sandboxed CLI run advertises only entries materialized and readable in its actual sandbox, while preserving the existing non-sandbox and native skills-plugin behavior.

Do we have a high-confidence way to reproduce the issue?

Yes from source with high confidence: current main supplies persisted snapshot XML to CLI startup prompts, while sandbox sibling paths rebuild entries from materialized workspace metadata. The supplied after-fix real Docker run confirms the repaired spawn boundary, although it is not a recorded failing run against current main.

Is this the best way to solve the issue?

Yes. Reusing the canonical sandbox workspace, skill filtering, and path-mapping helpers is the narrowest maintainable solution and avoids a competing CLI-specific rewrite or host-path fallback.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against e3a6da0f5181.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. Redacted runtime logs show the real CLI spawn receiving the remapped prompt and a real Docker container successfully reading the exact advertised materialized skill file.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (logs): Redacted runtime logs show the real CLI spawn receiving the remapped prompt and a real Docker container successfully reading the exact advertised materialized skill file.
  • remove rating: 🦪 silver shellfish: Current PR rating is rating: 🦞 diamond lobster, so this older rating label is no longer current.
  • remove status: 📣 needs proof: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P1: Affected sandboxed CLI sessions advertise unreadable skill files, breaking a real agent workflow until this fix lands.
  • merge-risk: 🚨 security-boundary: The PR changes model-visible skill path mapping across the host and sandbox boundary, even though its intended effect is to reduce host-path exposure.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (logs): Redacted runtime logs show the real CLI spawn receiving the remapped prompt and a real Docker container successfully reading the exact advertised materialized skill file.
  • proof: sufficient: Contributor real behavior proof is sufficient. Redacted runtime logs show the real CLI spawn receiving the remapped prompt and a real Docker container successfully reading the exact advertised materialized skill file.
Evidence reviewed

PR surface:

Source +78, Tests +100. Total +178 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 85 7 +78
Tests 1 100 0 +100
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 185 7 +178

What I checked:

  • Current-main gap: Current main passes the persisted skill snapshot prompt directly into CLI system-prompt construction, so the CLI path does not yet apply the sandbox materialization behavior already used by sibling paths. (src/agents/cli-runner/prepare.ts:472, e3a6da0f5181)
  • Canonical implementation boundary: The patch resolves the session sandbox workspace, then reuses the existing sandbox runtime-input, embedded-entry, and path-mapping helpers; it retains the existing snapshot resolver only when sandboxing is inactive. (src/agents/cli-runner/prepare.ts:107, ba8a42f967bd)
  • Sandbox lifecycle contract: The shared session-keyed workspace function owns sandbox layout/materialization and returns the container workdir, materialized skills workspace, eligibility, and workspace-access mode consumed by the patch. (src/agents/sandbox/context.ts:318, e3a6da0f5181)
  • Regression coverage: The added tests require the materialized /workspace skill path, reject the persisted host path, retain prompt-report accounting, preserve non-sandbox behavior, and omit duplicate XML when Claude's native skills plugin carries skills. (src/agents/cli-runner/prepare.test.ts:1830, ba8a42f967bd)
  • Real behavior proof: The supplied Azure Crabbox logs show the real CLI spawn receiving the system-prompt file, the prompt containing the materialized path rather than the host snapshot path, and docker exec successfully reading that exact file inside the sandbox. (src/agents/cli-runner/prepare.ts:166, ba8a42f967bd)
  • Validation state: The reviewed head passes the agent-runtime boundary check, real behavior proof gate, security scans, build, lint, production/test types, and relevant agent test lanes. (ba8a42f967bd)

Likely related people:

  • vincentkoc: Authored the merged backend prompt-workdir and custom-backend fallback commits for the materialized sandbox-skill path and merged the foundational PR. (role: feature owner and merger; confidence: high; commits: bb7fa7eb94f7, 25536314fe40, b71d8e1c32e3; files: src/agents/embedded-agent-runner/sandbox-skills.ts, src/agents/sandbox/context.ts, src/auto-reply/reply/commands-system-prompt.ts)
  • brokemac79: Authored the previously merged materialized sandbox skill-prompt fix, so they have established merged history on the exact behavior beyond proposing this follow-up. (role: introduced related behavior; confidence: high; commits: 483b78035b66, b71d8e1c32e3; files: src/agents/embedded-agent-runner/sandbox-skills.ts, src/auto-reply/reply/commands-system-prompt.ts)
  • Shakker: Current-main blame ties the CLI system-prompt construction and sandbox workspace context surface to recent work in commit 0fc2faa. (role: recent CLI preparation contributor; confidence: medium; commits: 0fc2faa0f48a; files: src/agents/cli-runner/prepare.ts, src/agents/sandbox/context.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@brokemac79
brokemac79 marked this pull request as ready for review June 12, 2026 18:28

Copy link
Copy Markdown
Contributor Author

@clawsweeper review

Please re-review now that the PR is marked ready. Focus area: sandboxed CLI startup prompts should use materialized /workspace/.openclaw/sandbox-skills/... paths instead of persisted host install skill snapshot paths. I am also running stronger Testbox/Crabbox proof and will update this PR with the run id/results.

@clawsweeper

clawsweeper Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Proof update added to the PR body.

Remote proof: Azure Crabbox fresh checkout of openclaw/openclaw#92508, provider azure, lease cbx_e221b1c09a31, run run_91015e15f9e6, machine Standard_D32ads_v6. The run installed Node/Corepack/pnpm, ran pnpm install --frozen-lockfile, then ran node scripts/run-vitest.mjs src/agents/cli-runner/prepare.test.ts; result: 39 tests passed.

This exercises the changed CLI prompt-building path with sandbox workspace metadata and verifies the generated system prompt uses /workspace/.openclaw/sandbox-skills/skills/gog/SKILL.md instead of the persisted /home/tzdai/.npm-global/lib/node_modules/openclaw/skills/gog/SKILL.md host path.

@brokemac79
brokemac79 force-pushed the codex/fix-sandbox-startup-skill-snapshot branch from 1f5470a to ba8a42f Compare June 12, 2026 19:08

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Added the requested real behavior proof to the PR body.

New proof: Azure Crabbox fresh PR checkout run_9af819d7701f, provider azure, lease cbx_61a51a2f9afd, slug brisk-shrimp, machine Standard_D32ads_v6, current head ba8a42f967bd23207b8db43dced89344a7a66b40.

The proof runs the real runCliAgent -> executePreparedCliRun spawn boundary with sandbox mode enabled and a text CLI backend implemented by a spawned Node process. The backend reads the system prompt file passed to the CLI process and then uses docker exec into the real Docker sandbox container to read the materialized skill path advertised in that prompt.

Key redacted output now in the body:

PROOF spawned_cli_pid=6156
PROOF system_prompt_file_arg=present
PROOF sandbox_container=oc-proof-proof-1781292949014-ca3ae7e4
PROOF materialized_path_in_prompt=/workspace/.openclaw/sandbox-skills/skills/gog/SKILL.md
PROOF host_snapshot_path_in_prompt=false
PROOF sandbox_skill_read_ok=true
PROOF sandbox_skill_read_bytes=155
PROOF sandbox_skill_read_error=

The same Crabbox run also reran node scripts/run-vitest.mjs src/agents/cli-runner/prepare.test.ts and passed 39 tests.

@openclaw-barnacle openclaw-barnacle Bot removed the proof: supplied External PR includes structured after-fix real behavior proof. label Jun 12, 2026
@clawsweeper

clawsweeper Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

The PR body proof section has been updated to the repository proof-gate schema (Evidence after fix, Observed result after fix, What was not tested). The latest Real behavior proof check is now passing: https://github.com/openclaw/openclaw/actions/runs/27438841788/job/81107574292

Please re-review against the existing spawned CLI + Docker sandbox evidence from Azure Crabbox run_9af819d7701f.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 12, 2026
@sallyom

sallyom commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

LGTM, autoreview clean, best narrow fix.

@sallyom
sallyom merged commit 8d9ce35 into openclaw:main Jun 12, 2026
215 of 227 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 13, 2026
michmill1970 added a commit to michmill1970/openclaw that referenced this pull request Jun 13, 2026
* fix(test): bound gateway harness teardown

* fix: repair update restart type checks

* test: stabilize full Mac regression suite

* fix(providers): preserve adaptive Claude routing

* fix(foundry): scope Entra tokens by API

* fix(foundry): use bearer auth for Claude

* fix(foundry): keep API-key Claude auth

* fix(foundry): align Claude onboarding contracts

* fix(foundry): infer selected Claude routes

* fix(foundry): clear API key auth on Entra setup

* fix(foundry): preserve Sonnet output limits

* fix(providers): apply auth patch deletions

* fix(bedrock): preserve Mythos Preview thinking policy

* fix(providers): preserve Mythos max routing

* fix(foundry): avoid stale setup metadata

* fix(types): narrow provider auth metadata

* fix(foundry): clamp Mythos Preview thinking

* fix(providers): bound Claude model matching

* test(anthropic): type Foundry auth fixtures

* fix(providers): preserve Claude output limits

* fix(providers): reconcile Claude profile routing

* fix(vertex): preserve Mythos thinking policy

* fix(foundry): route bearer auth from headers

* fix(providers): encode Mythos adaptive requests

* fix(foundry): preserve bearer auth intent

* fix(foundry): use bearer auth in native transport

* fix(vertex): default Mythos to adaptive thinking

* fix(foundry): preserve canonical thinking identity

* test(foundry): type auth lookup

* fix(foundry): align Claude thinking contracts

* fix(bedrock): keep optional thinking opt-in

* fix(bedrock): route Mythos through Mantle

* fix(bedrock): guard Mantle thinking options

* fix(foundry): label Anthropic Messages onboarding

* fix(foundry): make API labels exhaustive

* fix(foundry): expose Claude 4.6 max effort

* fix(foundry): bind auth and thinking contracts

* fix(foundry): type runtime auth result

* fix(auth): apply runtime request overrides everywhere

* fix(auth): apply runtime auth to labels

* fix(bedrock): normalize Mythos minimal effort

* fix(tts): use prepared completion auth

* test(tts): update prepared completion contract

* fix(anthropic): require Mythos adaptive thinking

* fix(tts): preserve async model discovery

* fix(anthropic): repair Mythos contract types

* fix(discord): scope command-deploy cache by application id (#77367)

* fix(discord): scope command-deploy cache by application id

Multi-bot Discord setups share a single command-deploy-cache.json under the
state dir. Cache keys were unscoped (`global:reconcile`, `guild:<id>`), so a
later account whose command set hashed identically to an earlier account would
hit the shared hash and skip its own application's command reconcile entirely
— Discord's Integration panel showed 'This application has no commands' for
the secondary bot even though gateway connect, application id, and token were
all valid.

Scope every cache key with `app:<clientId>:` so each Discord application
reconciles independently. Add regression tests covering: two applications with
identical command sets each call REST against their own application; a single
application with the same command set still hits the persisted cache; the
on-disk cache JSON contains application-scoped keys.

Fixes #77359.

* fix(discord): merge on-disk hashes inside persistHashes to survive concurrent writes

Codex follow-up on #77359 noted that server-channels.ts can start multiple
Discord deployers concurrently, so two deployers that both load the cache
file before either persists end up with the second writer overwriting the
first writer's app-scoped key — defeating the rate-limit cache that the
file exists to provide.

Inside persistHashes, re-read the on-disk cache and merge it with our
in-memory entries before the rename. Our in-memory entries always win on
key collisions (we just produced them); on-disk entries we don't have in
memory are preserved. Refresh in-memory state after the write so future
writes from the same deployer also keep entries other deployers added.

This is the lighter of the two repairs the codex review suggested
(re-read/merge vs serialize writes); it covers the realistic case where
one deployer writes before the other persists. Add a regression test that
exercises the load-then-other-deployer-writes-then-persist sequence.

* fix(discord): serialize command-deploy cache persists via in-process mutex

Codex follow-up on #77367 noted: re-read-before-write inside persistHashes
isn't enough — two deployers running persistHashes in true parallel can
both read the same snapshot before either writes, and the later rename
overwrites the earlier writer's app-scoped entries.

Add a module-level Map<storePath, Promise<void>> mutex and wrap the
read-merge-write cycle in withCachePersistLock so concurrent persists for
the same on-disk path serialize. In-process is sufficient because Discord
deployers only run inside the gateway process.

New regression test fires three deployers via Promise.all on the same
tick and asserts all three application-scoped entries survive — pre-fix
this race lost at least one entry.

* fix(discord): add override modifier on StaticCommand.description to satisfy strict TS

Current main enables noImplicitOverride; the StaticCommand test helper
re-declares the concrete BaseCommand.description property, which now
requires an explicit 'override' modifier (TS4114).

* test(discord): suppress typescript/unbound-method on vitest mock refs

The createRest() helper returns vi.fn() handlers cast as RequestClient,
so expect(rest.get).toHaveBeenCalledTimes(...) triggers
typescript/unbound-method 12 times. File-level disable: these are
vitest mock identities, not unbound class methods.

* fix(discord): clean up command cache lock

Signed-off-by: sallyom <[email protected]>

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: sallyom <[email protected]>

* feat(auto-reply): deliver inter-tool commentary as standalone verbose progress messages

When verbose progress is enabled, preamble item events now flush as durable
standalone progress messages through the same delivery path as tool summaries,
instead of living only in ephemeral channel streaming drafts. The latest text
per item id is buffered so snapshot-style producers send one message per item;
the buffer flushes when the producer moves on (next item, tool event, block
reply, or final reply) and drains before the final answer.

Verbose runs also force commentary classification on (commentaryProgressEnabled),
so inter-tool text routes to the commentary lane rather than being folded into
the final answer text.

Dispatch additionally exposes a live verbose-progress visibility getter via the
new onVerboseProgressVisibility reply option, so draft-rendering channels can
route progress to the durable lane while it is active.

* feat(telegram): route verbose progress payloads durably instead of into the streaming draft

With streaming on, the dispatcher diverted tool-kind payloads (including the
new durable commentary messages) into the ephemeral progress draft, where they
were discarded when the final answer arrived - so verbose runs lost their
progress record whenever streaming was enabled. While the durable verbose lane
is active (per the dispatch visibility getter), tool payloads are now sent as
real standalone messages and the draft yields its commentary lines; tool/plan
draft lines keep the draft since they have no durable counterpart. Reasoning
lane and tool status reactions are unaffected.

* feat(auto-reply): emit durable tool summaries from CLI runner tool results

The CLI parser already emits tool result events (name, toolCallId, isError,
sanitized result), but the runner bridge dropped them, so CLI-backed runs had
no durable tool record under verbose while embedded runs did. The bridge now
forwards result events, and both runners feed a summary tracker that renders
the same formatToolAggregate line the embedded runner emits (meta captured
from the start event args), plus the tool output block when full verbose
output is enabled. Delivery rides each runner's existing tool-result route, so
verbose gating, ordering ahead of the final answer, and the Telegram durable
routing all apply unchanged.

* fix(discord): yield the commentary draft while durable verbose progress is active

Discord consumes the dispatch verbose-progress visibility getter the same way
Telegram does: while the durable lane is delivering commentary as standalone
messages, the ephemeral progress draft skips its preamble lines so commentary
renders exactly once. Covered by an active/inactive regression pair.

* test(discord): add onVerboseProgressVisibility to dispatch params type

* refactor(auto-reply): distill verbose commentary lane wiring

* fix(sessions): preserve user model override across daily/idle rollover (#90128)

User-driven /model (and sessions.patch) overrides were dropped when a
session rolled over at the daily/idle reset boundary, reverting to the
configured default on the next turn despite the 'Model set to ... for
this session' ack. The override-preservation carryover in
initSessionState was gated on resetTriggered, so implicit stale
rollovers (the common case for always-on channel sessions) skipped it.

Run resolveResetPreservedSelection for any rollover that mints a new
session from an existing entry (explicit /new + /reset AND implicit
stale daily/idle). resolveResetPreservedSelection already preserves only
user-driven overrides and clears auto-fallback pins, so resets still
return to the default.

Adds regression tests in session.test.ts covering both cases.

Fixes #90119

Filed with AI assistance (OpenClaw agent); reviewed by @Peetiegonzalez.

Co-authored-by: Marvinthebored <[email protected]>

* fix(clickclack): allow explicit enable through plugin allowlist (#92084)

Allow an explicit canonical ClickClack enable/setup selection to record ClickClack in a nonempty plugin allowlist, while preserving unrelated allowlist rejection, denylist authority, and global plugin disablement.

Validated at source head 24af9d8e751440a8954aa731c5d2c63a53094698 with focused regressions, built-CLI disposable-config E2E, security checks, and autoreview. Merged under owner authorization despite the two documented untouched-main agent-core baseline failures.

* fix(auto-reply): stop dropping claude-cli narration when commentary lane is off

After #91976, the claude-cli JSONL parser reclassifies assistant text that
precedes a tool_use block as commentary. The classification gate
(commentaryProgressEnabled !== undefined) was looser than the delivery gate
(commentaryProgressEnabled === true && onItemEvent), so any channel that
defined the flag as false engaged classification with no consumer wired:
flushPendingClaudeCommentaryText() called an undefined onCommentaryText and
silently discarded the text. On Discord with verbose off this dropped all
inter-tool narration and the pre-final-answer preamble text.

Two-layer fix:
- Align the classify gate with the delivery gate in both CLI dispatch sites
  (agent-runner-execution, followup-runner) so classification only engages
  when a commentary consumer exists.
- Defense in depth: flushPendingClaudeCommentaryText() now falls back to the
  assistant text lane instead of discarding when no consumer is wired, so no
  future gate mismatch can silently eat model output.

Reported on Discord: claude-cli backend lost interleaved narration and the
regular-text reasoning preamble with or without /verbose on.

* refactor(agents): derive CLI commentary classification from consumer presence

* perf(ci): isolate Docker tooling tests

* perf(ci): isolate Docker tooling tests

* test(ci): align Anthropic agent expectations

* #92109: [Bug]: EmbeddedAttemptSessionTakeoverError caused by Btrfs ctimeNs instability (#92123)

* fix(session-lock): remove ctimeNs from session file fingerprint comparison

Btrfs background maintenance (snapshots, scrub, quota) updates ctime
without any file content change. Including ctimeNs in the fingerprint
causes false-positive EmbeddedAttemptSessionTakeoverError on all Btrfs
filesystems, breaking cron jobs and subagent spawns with 100% failure.

dev + ino + size + mtimeNs are sufficient to detect external writes —
any content change will also update mtimeNs and/or size. ctimeNs only
tracks metadata changes and adds no meaningful protection.

Closes #92109

* test(session-lock): cover ctime-only fence drift

* fix(session-lock): narrow ctime drift acceptance

* fix(session-lock): trust verified ctime drift

* fix(session-lock): bound ctime drift digest

* fix(session-lock): skip absent ctime digest

---------

Co-authored-by: Vincent Koc <[email protected]>

* fix(feishu): reply inside P2P direct-message threads (#92136)

* fix(feishu): keep P2P replies inside direct-message threads

* fix(clownfish): address review for ghcrawl-165996-agentic-merge (1)

* fix(clownfish): address review for ghcrawl-165996-agentic-merge (1)

Co-authored-by: LiaoyuanNing@TTC <[email protected]>

---------

Co-authored-by: vincentkoc <[email protected]>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: LiaoyuanNing@TTC <[email protected]>

* fix(memory): preserve live SQLite index during swaps

Fixes #91216.

Preserve the live memory SQLite index during atomic reindex swaps by publishing the POSIX main DB with an overwrite rename, keeping target WAL/SHM sidecars rollbackable until publish succeeds, and refusing no-create post-swap reopens that would otherwise auto-create an empty DB.

Verification:
- node scripts/run-vitest.mjs extensions/memory-core/src/memory/manager.atomic-reindex.test.ts extensions/memory-core/src/memory/manager-db-probe.test.ts extensions/memory-core/src/memory/manager.self-heal-missing-identity.test.ts extensions/memory-core/src/memory/manager-reindex-state.test.ts extensions/memory-core/src/memory/manager.fts-only-reindex.test.ts extensions/memory-core/src/memory/manager.readonly-recovery.test.ts
- git diff --check origin/main...HEAD
- node scripts/run-oxlint.mjs extensions/memory-core/src/memory/manager-atomic-reindex.ts extensions/memory-core/src/memory/manager.atomic-reindex.test.ts extensions/memory-core/src/memory/manager-db.ts extensions/memory-core/src/memory/manager-db-probe.test.ts extensions/memory-core/src/memory/manager-sync-ops.ts
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
- GitHub CI on 60df2b4178c8e16a301b715c77bc6197aced99d1

* Stabilize A2A prompt cache metadata (#90173)

A2A session routing identifiers are needed for delivery provenance, but concrete session keys in extraSystemPrompt make the agent system prompt vary between otherwise identical handoffs. Keep the model-facing system context stable by describing high-cardinality session slots with placeholders while retaining concrete values in inputProvenance. Channel names stay concrete: they are low-cardinality (discord/slack/webchat/...), so they do not meaningfully fragment the cache, and they inform reply formatting on the receiving agent.

Constraint: OpenClaw contributor PRs require focused behavior proof and tests for prompt/cache-facing changes.

Rejected: Removing routing metadata entirely | would weaken model context for requester/target roles.

Rejected: Placeholdering channel values too | drops model-visible formatting context for negligible cache benefit (reviewer feedback).

Confidence: medium

Scope-risk: narrow

Directive: Keep concrete session identifiers out of extraSystemPrompt; preserve them in structured provenance or payload fields. Low-cardinality channel labels may stay model-visible.

Tested: node scripts/run-vitest.mjs src/agents/tools/sessions-send-helpers.test.ts src/agents/openclaw-tools.sessions.test.ts

Tested: corepack pnpm exec oxfmt --check src/agents/tools/sessions-send-helpers.ts src/agents/tools/sessions-send-helpers.test.ts src/agents/openclaw-tools.sessions.test.ts

Tested: node scripts/run-oxlint.mjs src/agents/tools/sessions-send-helpers.ts src/agents/tools/sessions-send-helpers.test.ts src/agents/openclaw-tools.sessions.test.ts

Tested: git diff --check

Tested: live before/after provider cache trace (isolated local gateway, two A2A sends from distinct requester sessions; see PR Real behavior proof)

Co-authored-by: Sunjae Kim <[email protected]>

* fix(cli-runner): scope claude-cli queue to live-session owner identity (#91946) (#91974)

* fix(cli-runner): scope claude-cli queue to live-session owner identity

Fresh claude-cli runs without a stored cliSessionId previously collapsed
onto a single workspace-scoped queue key, serializing all fan-out within
one workspace regardless of subagent lane configuration.

Replace the workspace fallback with the same owner identity that
claude-live-session.ts already uses for its live-session map
(agentAccountId + agentId + authProfileId + sessionId + sessionKey),
keeping per-session resume safety while letting independent OpenClaw
sessions in the same workspace run concurrently.

Refactor buildClaudeLiveKey() to share the new buildClaudeOwnerKey()
helper so the queue key and the live-session key cannot drift.

Refs: #91946

* test(cli-runner): pin owner-key hash + document buildClaudeOwnerKey contract

Add a golden-hash regression test for buildClaudeOwnerKey using the
exact legacy fixture, so a future refactor that reorders fields or
flips the JSON encoding can't silently orphan every deployed Claude
live session at upgrade. Hash verified empirically against the prior
inline sha256(JSON.stringify(...)) in buildClaudeLiveKey.

Add a JSDoc on buildClaudeOwnerKey explaining the cross-module contract
between the CLI run queue and the live-session map.

Refs: #91946

* docs(cli-runner): tighten buildClaudeOwnerKey contract comment

The previous comment claimed an encoding mismatch would orphan deployed
live sessions across upgrades. The Claude live-session registry is
process-local, so any restart already discards every entry — the real
invariant is that the queue path and live-session path produce
byte-identical owner keys *within a single process*, so a fresh queued
turn picks up the same live session the registry already holds. Update
the helper docstring and the golden-hash test description accordingly;
the pinned hash and behavior are unchanged.

* test(cli-runner): add owner-key concurrency demo script

A pure-Node, no-test-runner demo that reproduces the PR-head queue
behavior end-to-end: BEFORE-PR collapse (workspace lane), distinct-owner
overlap, and identical-owner serialization, all in one run with
millisecond-stamped event ordering. Useful as a low-overhead regression
check for the owner-key contract and as a maintainer-runnable proof
artifact for #91946.

* test(cli-runner): satisfy oxlint curly + no-promise-executor-return

Wrap single-statement if/for-of bodies in braces and rewrite the
sleep helper so its Promise executor is a void block instead of an
arrow with an implicit return. No behavior change; demo output and
the byte-equivalent slice fingerprints are unchanged.

---------

Co-authored-by: wanglu241 <[email protected]>

* fix(thinking): apply Claude profile to anthropic-messages catalog rows (#92053)

* fix(thinking): apply Claude profile to anthropic-messages catalog rows

When a custom provider (e.g. `jdcloud-anthropic`) fronted Claude Opus over
the native anthropic-messages adapter, `--thinking xhigh` was silently
clamped to `off`. The thinking-profile dispatcher resolves bundled plugin
policy surfaces by exact provider id, so a renamed Anthropic-compatible
provider never reached the anthropic plugin's policy and `xhigh` was not
in the resulting profile.

`auto-reply/thinking.ts` already had a fallback keyed on
`context.api === "anthropic-messages"` that attached
`CLAUDE_FABLE_5_THINKING_PROFILE` for Fable models. Generalize it to use
`resolveClaudeThinkingProfile(modelId, params)` instead — the same
canonical helper the anthropic plugin uses — which still returns the Fable
profile for Fable models and now returns the correct Opus 4.7/4.8 profile
(with `xhigh`/`adaptive`/`max`) for Claude Opus regardless of provider id.

Non-Claude models on anthropic-messages routes still get the base
profile, and a Claude id on a non-Anthropic transport (e.g. an
openai-completions catalog row) is unaffected.

Fixes #91975

* fix(thinking): match native Anthropic includeNativeMax in fallback

Address ClawSweeper P2 review on #92053. The anthropic-messages fallback
in `resolveThinkingProfile` calls `resolveClaudeThinkingProfile` but
omits the `{ includeNativeMax: true }` option that the bundled anthropic
plugin uses (extensions/anthropic/provider-policy-api.ts:38,45).

For native-xhigh Claude families (Opus 4.7/4.8) this had no effect since
the native-xhigh branch already exposes `max`. But adaptive Claude
families that take the adaptive-default branch (e.g. claude-sonnet-4-6,
claude-opus-4-6) silently lost `max` parity on custom anthropic-messages
providers compared to native Anthropic policy.

Also add a regression test on `claude-sonnet-4-6` that verifies the
adaptive-branch path keeps `max` for custom providers.

* docs(thinking): document deliberate compat.xhigh bypass on anthropic-messages

Self-review surfaced a subtle behavior change worth documenting: when the
anthropic-messages fallback was generalized, non-Claude models on this
transport stop honoring catalog `compat.supportedReasoningEfforts: ["xhigh"]`
because they take the Claude base profile instead of falling through to the
later `catalogSupportsXHigh` upgrade path.

This is intentional — anthropic-messages does not carry a generic xhigh
contract; xhigh on this protocol is a Claude-family capability. Add an
inline comment at the resolver site and a regression test that locks the
suppression so the next reader (or a future patch) doesn't accidentally
restore the upgrade path.

* fix(thinking): extract Claude profile to leaf to break import cycle

The previous commits added a `resolveClaudeThinkingProfile` import from
`auto-reply/thinking.ts` to `plugin-sdk/provider-model-shared.ts`. The
shared barrel re-exports `provider-replay-helpers` and `plugins/types`,
which transitively reach back into `auto-reply` via the gateway server
methods chain — creating the madge cycle reported by
`check:madge-import-cycles`:

    auto-reply/thinking.ts
      -> ... -> plugin-sdk/provider-model-shared.ts
      -> plugins/{config-schema, host-hooks, ...} -> plugins/types.ts

Move `BASE_CLAUDE_THINKING_LEVELS`, `isClaudeAdaptiveThinkingDefaultModelId`,
and `resolveClaudeThinkingProfile` to a new leaf module
`src/plugins/provider-claude-thinking.ts` whose only imports are
`@openclaw/llm-core` and the existing leaf `provider-thinking.types`.

`provider-model-shared.ts` continues to re-export both helpers so existing
consumers (`extensions/anthropic/*`, the public test surface) are
unaffected. `auto-reply/thinking.ts` now imports the leaf directly,
breaking the cycle.

* test(thinking): add live proof harness for #91975 anthropic-messages clamp

---------

Co-authored-by: wanglu241 <[email protected]>

* Google: show detailed Gemini CLI OAuth extraction failures (#41991)

* Google: preserve Gemini CLI OAuth failure context

Port the diagnostics-only fix to the bundled Google OAuth implementation so user-facing setup errors explain why automatic Gemini CLI client-config discovery failed.

Constraint: #54289 may remove or gate automatic Gemini CLI credential extraction
Rejected: Change extraction consent behavior here | security/product decision belongs in #54289
Confidence: medium
Scope-risk: narrow
Tested: pnpm test -- extensions/google/oauth.test.ts
Tested: pnpm check
Tested: pnpm format:check extensions/google/oauth.credentials.ts extensions/google/oauth.test.ts
Not-tested: full pnpm test suite

* Google: clarify Gemini bundle fallback diagnostic comment

Keep the follow-up limited to the explanatory comment so it matches the diagnostic error preservation added around bundle traversal failures.

Constraint: comment-only cleanup after diagnostics port
Confidence: high
Scope-risk: narrow
Tested: pnpm format:check extensions/google/oauth.credentials.ts
Not-tested: tests not run; comment-only change

* fix: resolve OAuth test rebase conflict

* fix(qqbot): flush tool output before silent non-streaming final (#92074)

* fix(qqbot): flush tool output before silent non-streaming final

* fix qqbot silent final delivery

* chore: drop local plugin runtime helper

* fix: suppress stale qqbot tool flush

* fix(qqbot): flush tool output before silent non-streaming final (#92074) (thanks @sliverp)

---------

Co-authored-by: Ubuntu <[email protected]>

* fix(agents): forward channel identity to CLI hooks

* fix(models): clarify provider model registration hint (#89508)

Co-authored-by: Cornna <[email protected]>

* fix(agents): keep migrated session entry ids unique on v1 upgrade (#89085)

migrateV1ToV2 assigned each entry id via generateId(ids) but never added the
result back into ids, so the collision-check set stayed empty for the whole
migration and generateId's check was a no-op. A v1 to v2 upgrade could then mint
two entries with the same 8-hex id, and because the migration rebuilds the
parent/child tree from those ids it would parent the second entry to itself,
corrupting the branch. Add ids.add(entry.id) so the generator sees prior ids and
retries on collision.

Adds a regression test that drives the real SessionManager.open migration path
with a seeded id collision.

* fix(discord): clean migrated thread binding state (#89552)

* fix(discord): clean migrated thread binding state

* fix(discord): omit undefined thread binding fields

---------

Co-authored-by: Hex <[email protected]>

* fix(cron): reject durations that overflow to a non-finite value (#89448)

* fix(cron): reject durations that overflow to a non-finite value

parseDurationMs guarded the parsed mantissa but returned Math.floor(n * factor)
with no finite check on the product. A finite mantissa times a large unit factor
(e.g. "1e302d", factor 86_400_000) overflows to Infinity, which was returned as
the millisecond value. Reject a non-finite result instead, matching the existing
contract that already rejects non-finite / non-positive mantissas.

Fixes #83906.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* ci: rerun flaky runner checks

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>

* fix(doctor): warn on unsupported hook entry loaders (#89319)

* fix(doctor): warn on unsupported hook entry loaders

* fix(doctor): guard null hook entry configs

* fix(doctor): guard null hook entry configs

* fix(doctor): repair misplaced hook loader paths

* fix(doctor): clarify unsupported hook entry repair

---------

Co-authored-by: Vincent Koc <[email protected]>

* fix(qqbot): guard silent-final tool flushing

* fix(config): stop config.patch replacePaths index suffix from widening array consent (#91966)

* fix(config): stop config.patch replacePaths index suffix from widening array consent

normalizeConfigPatchReplacePath stripped a trailing array bracket, so an entry/index-scoped token like bindings[0] or bindings[] collapsed onto the bare whole-array token (bindings). That bare token is both the merge replaceArrayPaths key and the destructive-array gate's exact-path token, so an index-scoped consent silently authorized a full-array replacement and dropped unrelated base entries on the gateway config.patch path, and the same collapse let the agent self-edit tool truncate id-keyed arrays whenever no protected path happened to be involved.

Keep the interior index normalization (agents.list[0].skills -> agents.list[].skills) but no longer collapse a trailing bracket, so a bracket/index-suffixed token never matches the bare whole-array token and the destructive-array gate stays fail-closed unless the documented exact path is passed. Update the agent-tool test whose expectation depended on the old collapse: agents.list[0] now does a non-destructive id-keyed merge that only changes model and is correctly allowed.

* fix(config): distinguish indexed and array replace consent

* test(config): cover replace consent syntax

* fix(config): make replace path normalization idempotent

---------

Co-authored-by: Vincent Koc <[email protected]>

* fix(plugins): stop derived metadata snapshot rescan storm in /models (regression shipped since v2026.5.18) (#92127)

Since 5734193fdf ("fix(plugins): keep metadata snapshot memo fresh",
first shipped in v2026.5.18), the in-process plugin metadata snapshot
memo stores derived-registry results under a key recomputed from the
freshly built snapshot.index, while lookups key off the persisted-index
registry state. On installs where the registry resolves as "derived"
(persisted index absent or not covering the running checkout), the two
keys never match. Worse, the lookup-side adoption loop returns the most
recently stored registryState for the context, so two alternating call
shapes (e.g. the model-catalog build mixing workspace-scoped and global
lookups) each adopt the other shape's state, compute a key that was
never stored, and re-run the full plugin manifest scan - on every call,
forever. Chat /models (and each subsequent provider/model pick) pays
multiple full manifest scans plus all downstream snapshot-identity cache
invalidation per step, pinning a CPU core for seconds on every
interaction, in every chat channel.

Fix: store the memo under the exact memoKey/registryState the call
looked up by, instead of re-deriving a second key from snapshot.index.
Freshness is unchanged - the lookup context hash and the plugin metadata
lifecycle clears (install/reload/doctor) still own invalidation. The
now-unused index parameter of resolvePersistedRegistryMemoState is
removed.

Measured on the author's VPS (real plugin discovery, identical catalog
output of 263 entries on both sides): the full-discovery model catalog
build behind chat /models dropped from ~6.3s to ~0.3s (~21x), with
repeat snapshot lookups going from full rescans to memo hits.

Regression test: alternating derived call shapes must not re-scan
(red on main: 4 scans; green with this fix: 2).

* fix(ollama): use provider thinking default in SDK session factory (#91657)

* fix(ollama): use provider thinking default in SDK session factory

* fix(agents): preserve model metadata for thinking defaults

* fix(agents): resolve custom Ollama thinking policy

---------

Co-authored-by: Vincent Koc <[email protected]>

* fix(memory): abort orphaned embedding work when memory_search times out (#91742)

* fix(memory): abort orphaned embedding work when memory_search times out

memory_search raced its 15s deadline with Promise.race and returned a clean
timeout to the agent, but the underlying embedQueryWithRetry loop kept
retrying (3 attempts x 60s) against the embedding backend with no consumer.
Thread the tool-owned AbortSignal through manager.search ->
embedQueryWithRetry -> runEmbeddingOperationWithTimeout so the deadline
cancels in-flight embedding work, stops the retry loop, and skips
fallback-provider activation for an absent caller.

Fixes #91718

* fix(memory): let the deadline result win before aborting the search

Abort listeners dispatch synchronously, so an abort-aware search could
reject the raced task before the timeout promise resolved and replace the
stable 'memory_search timed out after 15s' result with a provider-wrapped
abort error. Resolve the timeout first, then abort.

* fix(memory): scope deadline abort to builtin embeddings

* fix(memory): preserve deadline signal across fallback

---------

Co-authored-by: Vincent Koc <[email protected]>

* fix(memory-core): retry narrative message reads (#89091)

* fix(memory-core): retry narrative message reads

* fix(memory-core): wait longer for narrative text

* fix(release): gate beta publish on plugin verification

Delay public GitHub release publication until postpublish verification, dependency evidence upload, proof append, and required plugin publish gates pass.

Also updates release-maintainer instructions so newly publishable plugins are minted/prepublished through an owner-approved path without consuming the next auto-bumped beta version unless that path is the actual release publish.

* fix(cli): validate gateway RPC timeout inputs

Reject malformed or explicit empty Gateway RPC timeout values before opening Gateway calls, align the shared Gateway RPC omitted-timeout fallback with the 30000 ms CLI default, and validate explicit `cron add --timeout-seconds` values at the CLI boundary.

Carries forward the useful source work from #54646 and the earlier timeout-validation context from #40953. #60661 remains separate accepted-run timeout semantics work and is intentionally not folded into this change.

Validation:
- `npm run review-results -- /tmp/clownfish-check-27341769444`
- `git diff --check`
- OpenClaw PR checks on `ce7bd8b9388a5689b14ddc2b3a984f7b4647e5ca`: 132 pass, 0 pending, 0 failing
- ClawSweeper re-review: https://github.com/openclaw/clawsweeper/actions/runs/27344244608

Co-authored-by: RayRuan <[email protected]>
Co-authored-by: Homeran <[email protected]>

* fix(agents): retry same model across short rate-limit windows (#91911)

Bound same-model rate-limit retries to explicit short-window signals or parsed short Retry-After values, honor Retry-After in the retry sleep, preserve zero-rotation fallback behavior, and record same-model rate-limit retries separately from profile rotations.

Verification:
- node scripts/run-vitest.mjs src/agents/embedded-agent-runner/run/assistant-failover.test.ts src/agents/embedded-agent-runner/run/helpers.test.ts
- Azure Crabbox cbx_bdb5a7807a1f / coral-shrimp: OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 corepack pnpm check:changed
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main

* fix(agents): project thinking catalog compat

* test(ci): relax docker signal wait

* chore: remove redundant proof scripts

* fix(cron): report SQLite storage path in cron.status instead of legacy jobs.json (#92144)

* fix(cron): report SQLite storage path in cron.status instead of legacy jobs.json

The `cron.status` gateway response returned `storePath` pointing to the
legacy `jobs.json` path, but cron jobs are actually stored in the shared
SQLite state database. This misled operators and agents into looking for
a JSON file that no longer exists.

- Add `storage: "sqlite"` and `sqlitePath` fields to CronStatusSummary
- Mark legacy `storePath` as @deprecated (kept for backward compat)
- Update CLI warning to prefer sqlitePath over storePath
- Add regression assertions in read-ops test

Fixes #91766

* fix(macos): prefer sqlitePath in cron status display

* fix(macos): add sqlitePath to CronSchedulerStatus type

* fix(channel): harden local setup trust (#92175)

Summary:
- The PR extends channel setup trust enforcement and trusted catalog fallback from workspace-origin plugins to ... nfigured load paths into catalog discovery, and adds focused regression plus Docker/package proof coverage.
- PR surface: Source +190, Tests +892, Other +324. Total +1406 across 13 files.
- Reproducibility: yes. The source PR provides a concrete clean-main Docker/package path where an explicitly t ... ns unresolved, while the patched package resolves it and still blocks untrusted module and setup execution.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(channel): stabilize trusted catalog dts typing
- PR branch already contained follow-up commit before automerge: fix(channel): repair trusted catalog exclusions typing
- PR branch already contained follow-up commit before automerge: test(channel): cover local channel plugin trust
- PR branch already contained follow-up commit before automerge: chore(deps): refresh plugin shrinkwraps
- PR branch already contained follow-up commit before automerge: test(channel): route trust regression in command shard
- PR branch already contained follow-up commit before automerge: test(channel): remove e2e-named trust regression

Validation:
- ClawSweeper review passed for head eabee04d54596790a16a70d56b97b51eddd87791.
- Required merge gates passed before the squash merge.

Prepared head SHA: eabee04d54596790a16a70d56b97b51eddd87791
Review: https://github.com/openclaw/openclaw/pull/92175#issuecomment-4680798117

Co-authored-by: Mason Huang <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: hxy91819
Co-authored-by: hxy91819 <[email protected]>

* fix(installer): stop after failed Node package installs

Linux Node package-manager setup/install failures now fail the installer immediately instead of falling through to a misleading success path. Adds regression coverage for NodeSource setup and apt nodejs install failures under conditional shell invocation.\n\nFixes #73837\n\nProof: bash -n scripts/install.sh; node scripts/run-vitest.mjs test/scripts/install-sh.test.ts; node scripts/run-oxlint.mjs test/scripts/install-sh.test.ts; git diff --check origin/main...HEAD; autoreview clean; Azure Crabbox check:changed cbx_6286dc1e287b passed.

* fix(wizard): report keyless web search providers as ready

Onboarding finalize now treats configured web search providers with requiresCredential: false as ready instead of warning that an API key is missing. This covers keyless providers such as Parallel Search (Free), DuckDuckGo, and Ollama while preserving credential-required warnings for providers that need keys.\n\nProof: focused wizard/search tests; oxlint on changed files; git diff --check; autoreview clean; Azure Crabbox check:changed cbx_b92ef084c21c passed; GitHub checks green.

* fix: handle explicit silent assistant replies (#92073)

Signed-off-by: sallyom <[email protected]>

* feat(skills): allow trusted workshop symlink targets

* fix: gate Skill Workshop symlink writes

* fix: validate workshop support symlink writes

* test: harden stalled websocket cleanup

* refactor(whatsapp): introduce inbound message contexts

* refactor(whatsapp): read inbound contexts in auto reply

* test(whatsapp): update auto reply inbound fixtures

* test(whatsapp): cover inbound context compatibility

* docs(plugins): record whatsapp inbound compatibility

* fix: keep whatsapp inbound aliases live

* test: clean whatsapp alias assertions

* test: narrow whatsapp mention fixture

* refactor: move workspace skill writes to lifecycle

* fix: preflight skill writes before rollback metadata

* fix: avoid support write shadowed variable

* fix: preserve utils exports in doctor health tests

* fix: rely on ClawHub plugin publish checks

* test(sqlite): add state perf query plan harness

Adds a SQLite state query-plan regression test and smoke benchmark, wires the smoke artifact into source performance evidence, validates SQLite smoke output in the performance summary, and removes a retired ClawHub nav entry that broke docs link checks.

Fixes #91616

* fix(daemon): keep unsupported service status readable

Fixes #25621.\n\nKeep gateway status readable on unsupported service-manager platforms by returning a conservative read-only service adapter, while lifecycle mutations still reject clearly. Includes regression coverage for resolver, status, summary, and lifecycle behavior.\n\nVerified with focused Vitest/oxlint/diff checks, autoreview, and Azure Crabbox check:changed on lanes core/coreTests.

* fix(cron): preserve timezone on expression edits

Fixes #92291.

Preserves the existing cron timezone when `cron edit <id> --cron ...` replaces only the expression, while avoiding stale stagger writes and preserving API/UI omitted-timezone clearing semantics.

Proof:
- node scripts/run-vitest.mjs src/cli/cron-cli/register.cron-edit.test.ts src/cli/cron-cli.test.ts src/cron/service.jobs.test.ts src/cron/normalize.test.ts
- node scripts/run-oxlint.mjs src/cli/cron-cli/register.cron-edit.ts src/cli/cron-cli/register.cron-edit.test.ts src/cli/cron-cli.test.ts src/cron/service/jobs.ts src/cron/service.jobs.test.ts
- git diff --check origin/main...HEAD
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
- Azure Crabbox cbx_3ce6a4e0d945 / silver-lobster: OPENCLAW_TESTBOX=1 node scripts/crabbox-wrapper.mjs run --provider azure --class Standard_D4ads_v6 --idle-timeout 90m --ttl 240m --timing-json -- env OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 corepack pnpm check:changed

* fix(docker): bundle QA Lab runtime in the image (#92087)

* fix(docker): split qa lab runtime fixes

* fix(docker): remove store platform selector

* test(docker): assert qa lab ui copy is gated

* fix(docs): remove stale ClawHub nav page

* fix(release): use trusted publishing for plugin npm

* refactor(telegram): centralize edit error classification in network-errors

* fix(telegram): retry transient draft preview failures instead of killing the stream

* fix(telegram): carry bot api error codes across the ingress worker boundary

* fix(telegram): restart isolated polling on getUpdates conflict and surface it in status

* fix: scope image inbound state env

* test: scope reply session state env

* fix: add test env delete helper

* test: scope doctor missing state env

* fix: restore commitment store state env

* test: restore commitment runtime state env

* fix: restore commitment extraction state env

* test: scope commitment full chain state env

* fix: scope commitment heartbeat state env

* test: route state dir env helper

* fix: route live env restore deletes

* test: scope plugin dispatch state env

* fix: route respawn hint env clears

* fix(anthropic-vertex): stop re-marking cache_control on transport-budgeted payloads (#92387)

Summary:
- The PR removes the Anthropic Vertex adapter’s redundant cache-control payload-policy pass, forwards caller payload hooks unchanged, and adds regressions for preserving transport-budgeted payloads.
- PR surface: Source -35, Tests -11. Total -46 across 2 files.
- Reproducibility: yes. at source level. Current main reapplies cache policy to a finalized, fully budgeted pa ... ion logs show the corresponding five-marker rejection; this review did not run a live post-fix GCP request.

Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.

Validation:
- ClawSweeper review passed for head 6ef19602bfb48a819e096b23b66ece94a8072ed4.
- Required merge gates passed before the squash merge.

Prepared head SHA: 6ef19602bfb48a819e096b23b66ece94a8072ed4
Review: https://github.com/openclaw/openclaw/pull/92387#issuecomment-4688955121

Co-authored-by: openperf <[email protected]>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <[email protected]>

* fix: stop docker build commands by pid and group

* fix doctor channel SecretRef preview (#92229)

* Fix disabled heartbeat one-shot cron retries (#92225)

* fix: retry disabled cron wake one-shots

* fix: satisfy cron retry CI checks

* fix: inherit static transport for configured DeepSeek models (#92265)

* fix: fail closed for cli-backed btw fallback (#92226)

* fix heartbeat suppressed commitment delivery (#92231)

* fix(agents): classify structured unsupported model errors (#92280)

* fix(agents): classify structured unsupported model errors

* test(agents): update embedded harness helper mock

* Fix OTLP log trace correlation (#92276)

* fix diagnostics otel log trace correlation

* test diagnostics trace provenance contract

* fix(update): hand off Linux service auto-updates (#92282)

* fix: resolve managed secretref provider auth (#92235)

* fix provider static model fallback (#92293)

* fix(agent): continue after source message tool replies (#92343)

* fix(codex): preserve memory prompt registration (#92350)

* fix(codex): restore memory recall guidance

* fix(codex): add memory recall fallback

* fix(codex): preserve memory prompt registration

* test(codex): expect memory slot in scoped harness load

Signed-off-by: sallyom <[email protected]>

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: sallyom <[email protected]>

* fix: clarify gateway SecretRef auth diagnostics (#92290)

* fix gateway secretref health diagnostics

* fix gateway health result type narrowing

* fix: repair rejected Anthropic thinking replay (#92286)

* fix: repair rejected Anthropic thinking replay

* fix: narrow recovered retry result

* test: satisfy thinking recovery lint

* test: prove thinking retry preserves fresh reasoning

* test: type narrow thinking retry proof

* Fix Telegram spooled buffered replay (#92281)

* fix telegram spooled buffered replay

* fix telegram replay type checks

* fix telegram replay lint

* test telegram replay visible output retry guard

* fix telegram rollback failure retry

* fix(doctor): show per-step progress spinners during update

* test(doctor): cover update progress wiring and spinner cleanup

* fix(doctor): drop redundant Boolean conversion flagged by oxlint

* test: tighten doctor update progress coverage

* fix(outbound): honor top-level image param as send media source (#92407) (#92416)

When a message send action included an `image` media-source param, the shared outbound runner recognized it for sandbox validation and media-access hints but then omitted it from the generic send payload, causing text-only delivery with a silent ok:true result.

Add `image` to the mediaHint resolution chain in buildSendPayloadParts so it is treated as a first-class media source for send only, preserving action-specific image semantics for non-send actions. Add regression coverage.

Fixes #92407.

* fix(sandbox): render cli skill prompts from materialized paths (#92508)

* Updated fallback logic

* fix: update esbuild audit pin (#92540)

* Add QA evidence artifact output (#91484)

* feat: add qa evidence summary normalization

* chore: rename qa evidence target environment

* chore: align qa evidence profile terminology

* chore: align qa evidence summary fields

* chore: add qa evidence taxonomy ref

* test: remove stale multipass evidence example

* test(qa): normalize vitest and playwright evidence

* test(qa): slim evidence summary metadata

* test(qa): clarify evidence summary inputs

* test(qa): rename scenario specs in evidence flow

* test(qa): treat evidence profiles as mapping strings

* test(qa): use neutral evidence test identity

* test(qa): nest evidence summary joins

* refactor(qa): normalize live evidence summaries

* fix(qa): accept normalized telegram rtt summaries

* fix(qa): normalize evidence lane summaries

* fix(qa): align evidence summaries with requirements

* refactor(qa): tighten evidence summary builders

* refactor(qa): restore standard evidence ids

* fix(qa): keep legacy summaries out of rtt evidence

* refactor(qa): make package evidence provenance explicit

* test(qa): keep script tests out of qa lab internals

* refactor(qa): rename scenario evidence definitions

* refactor(qa): clean evidence summary wording

* test(qa): fix evidence summary test inputs

* refactor(qa): simplify evidence identity fields

* refactor(qa): tighten evidence summary inputs

* refactor(qa): rename evidence artifact

* Add QA scorecard taxonomy validation (#91500)

Merged via squash.

Prepared head SHA: a9aec907d4823ad24fe06edf7e66d5365a4f2343
Co-authored-by: RomneyDa <[email protected]>
Co-authored-by: RomneyDa <[email protected]>
Reviewed-by: @RomneyDa

* fix(telegram): allow expandable blockquotes

* test: cover telegram expandable blockquotes

* feat(moonshot): add Kimi K2.7 Code support (#92554)

* feat(moonshot): add Kimi K2.7 Code support

* test(moonshot): surface K2.7 live provider errors

* ci(live): accept Kimi key for Moonshot sweeps

* test(moonshot): verify K2.7 across API regions

* fix(moonshot): backfill reasoning_content on assistant tool-call replay messages (#92396)

Moonshot/Kimi requires reasoning_content on all assistant tool-call messages
when thinking is enabled. After LCM compaction, cross-model fallback, or
session repair, the replayed history may be missing this field, causing a
400 error from the Moonshot API.

Backfill an empty string to satisfy the API schema contract without
fabricating semantic reasoning content. Follows the same provider-owned
backfill pattern already used by Kimi Coding (extensions/kimi-coding/stream.ts)
and DeepSeek V4 (provider-stream-shared.ts).

Fixes #71491

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>

* fix(e2e): keep lifecycle timeout cleanup alive (#92566)

* ci: split plugin ClawHub publishing paths

* feat: partition clawhub plugin release candidates

* fix: read clawhub trusted publisher config endpoint

* feat: split clawhub plugin bootstrap workflow

* ci: split plugin clawhub publish paths

* ci: pin clawhub package publish workflow

* ci: keep clawhub bootstrap token out of builds

* ci: fix clawhub release dry-run gating

* ci: align clawhub oidc publish refs

* ci: make clawhub bootstrap recovery idempotent

* ci: route clawhub repair candidates through bootstrap

* ci: preserve tideclaw alpha clawhub guards

* ci: simplify clawhub release ref handling

* ci: extract clawhub release routing plan

* ci: extract clawhub release runtime state

* test: guard clawhub release helper executability

* ci: pin ClawHub CLI for plugin publishing

* ci: allow historical ClawHub dry-run validation

* ci: fix ClawHub bootstrap token handoff

* fix(gateway): mirror commentary-phase assistant events to session message subscribers

Non-control-UI-visible runs previously dropped assistant commentary on the
floor for session message subscribers. Mirror those events to exact session
subscribers, gated strictly on phase === "commentary" so untagged text or
delta frames and final-answer streaming never dual-lane into channel
surfaces. Dialects that emit commentary as untagged deltas should tag the
phase at provider normalization instead.

Co-authored-by: Forge <[email protected]>
Co-authored-by: Chisel <[email protected]>

* fix: harden websocket payload handling

* fix(moonshot): rewrite duplicate native Kimi tool call ids

Preserve the first native Kimi tool-call ID while rewriting repeated replay occurrences to deterministic OpenAI-style IDs and keeping paired tool results aligned. Moonshot responses-family behavior and providers that do not opt in remain unchanged.

Closes #51593

Co-authored-by: Pluviobyte <[email protected]>

* Expose paged channel action results (#88993)

* fix(discord): expose paged thread list results

* fix(discord): keep thread pagination local

* fix(discord): use archive timestamps for thread cursors

* test(discord): cover thread pagination results

---------

Co-authored-by: Peter Steinberger <[email protected]>

* fix(fireworks): resolve catalog model params from manifest (#90326)

Resolve bundled Fireworks manifest models through core's static catalog so Kimi K2.6 keeps its 262,144-token context limit and nested model compatibility metadata.

Keep the existing dynamic fallback for uncataloged Fireworks IDs and align bundled Kimi reasoning metadata with existing runtime behavior.

Verified with focused tests, extension/core type checks, lint/format, full build, fresh autoreview, required CI, and a live Fireworks Kimi K2.6 embedded run using a real key.

Co-authored-by: Evgeni Obuchowski <[email protected]>

* fix(doctor): diagnose blocked external channel plugins (#86629)

Diagnose configured channel plugins whose installed owners are blocked by trust or activation policy, while preserving multi-owner fallback and actionable channel safety warnings.

Closes #83212.

Co-authored-by: luyifan <[email protected]>

* fix(mistral): skip unreadable tool schemas (#90242)

Skip unreadable Mistral tool schemas while preserving valid sibling tools. Omit empty tool payloads, reject forced choices for removed tools, and snapshot pinned tool names before validation and emission.

Live Mistral E2E: run_9b34d30e9f5b
CI: https://github.com/openclaw/openclaw/actions/runs/27459679633

Co-authored-by: Vincent Koc <[email protected]>

* fix(slack): persist delivered replies in transcripts (#92498)

Persist successful same-channel Slack and CLI assistant replies exactly once in the owning transcript. Preserve delivery-hook output, routed/runtime ownership, custom stores, and authoritative reset/session rotation bindings.

Fixes #92489

Co-authored-by: Andy Ye <[email protected]>

* fix(channels): report progress draft startup failures (#92083)

Report timer-fired channel progress-draft startup failures at the shared gate boundary instead of silently dropping them. Explicitly awaited starts still reject, and failed timer starts remain retryable.

Refs #92031.

Co-authored-by: Hansraj Singh Thakur <[email protected]>

* fix(agents): isolate invalid plugin model catalogs (#92564)

Keep valid root and plugin models available when one generated plugin catalog is invalid, while retaining and logging the catalog error.

Fixes #92553.

Co-authored-by: tangtaizong666 <[email protected]>

* chore(maint): make PR changelog edits release-only (#92607)

* docs: UX-013 — design system documentation (#89827)

* docs: UX-013 — shared design system documentation

* docs: align design system docs with source tokens

* feat(ui): hide empty workboard columns (#89615)

* fix(a11y): B-1+B-2+B-3 — contrast, focus states, minimum font sizes (#89822)

* fix(a11y): B-1 — raise muted text contrast to ≥4.8:1 WCAG AA

* fix(ui): C-1 — ChatSidePanel joins glass surface language

* feat(mobile): D-1 — hamburger overlay nav below 900px

- Esc key now closes nav drawer (globalKeydownHandler)
- Nav item tap targets bumped to min-height: 44px + padding: 10px 16px
  in the ≤1100px drawer breakpoint (was 40px / 0 12px)
- Hamburger toggle + overlay drawer were already wired in app-render.ts;
  this completes close-on-Esc and ensures accessible tap targets

* fix(a11y): B-2 — consistent focus-visible states distinct from hover

* fix(a11y): B-3 — lift all sub-12px text to 12px minimum

* fix(a11y): narrow focus and CSS scope

* fix(a11y): finish focus-visible selectors

* fix(memory): preserve qmd startup errors (#92618)

* docs(gateway): add uptime monitoring guidance to health check docs (fixes #55768) (#92608)

* fix(docs): pin Windows Hub download links to v2026.6.5 (#92605)

The Windows Hub companion installers are promoted to the main OpenClaw
release via a manual workflow_dispatch, not every release includes them.
The /releases/latest/download/ links resolved to v2026.6.6 which does not
have the OpenClawCompanion assets, causing 404 errors.

Pin the links to v2026.6.5 (the latest release that has the assets) and
add a fallback note directing users to the releases page when a release
is missing the companion installers.

Fixes #92470

* #92589: fix(internal-runtime-context): wrap prompt-preface runtime context body in delimiters (#92593)

* fix(internal-runtime-context): wrap prompt-preface runtime context body in delimiters

When buildRuntimeContextMessageContent constructs the hidden
runtime context prompt block, the body (which may contain
sensitive metadata like relevant-memories, sender info, and
conversation metadata) was not wrapped in the standard
INTERNAL_RUNTIME_CONTEXT_BEGIN/END delimiters.  If the model
echoed this context back in its reply, stripInternalRuntimeContext
could only remove the header and notice lines — the sensitive
body leaked through to user-visible surfaces like Feishu
streaming cards.

Wrap the runtime context body in BEGIN/END delimiters so the
existing stripInternalRuntimeContext (which handles delimited
blocks first) can fully remove the entire block.

Closes #92589

* chore: retrigger CI for proof check

* chore: retrigger CI with corrected proof format

* chore: retrigger CI with corrected proof field format

* Run Vitest and Playwright scenarios from qa suite (#92606)

* test(qa): run vitest and playwright scenarios from qa suite

* fix(qa): harden scenario suite dispatch

* refactor(qa): share scenario path utilities

* refactor(qa): share test file scenario runner

* refactor(qa): route test file scenarios through suite runtime

* refactor(qa): use explicit suite runtime result kind

* test(qa): write suite evidence artifact

* refactor(qa): clarify suite execution dispatch

* fix(qa): keep test-file scenarios out of flow-only runners

* refactor(qa): export mixed scenario suite runner

* Revert "chore(maint): make PR changelog edits release-only (#92607)"

This reverts commit 4640baa299099df01bf58b040c8b6ae9f243713e.

* feat(hooks): per-turn usageState (with provider limits + rich atoms) on reply_payload_sending

Attach a per-turn execution snapshot to the reply_payload_sending hook as
`usageState`, so a plugin (or the future in-core /usage renderer) can render a
per-response usage readout as a pure consumer of the contract — no side calls.

Recorded in agent-runner, consumed in dispatch. Fields: provider, model,
resolvedRef/requested, reasoningEffort, fastMode, fallbackUsed, is_override
(overrideSource), authMode, compactionCount, contextTokenBudget, token usage,
turn cost (USD), duration, owning agentId/sessionId, chatType, the agent
identity (name/emoji), and the active provider's subscription `limits` windows.

reply_payload_sending is the one reply hook universal across every surface
(incl. the Codex app-server, which emits no llm_output/agent_end), so it is the
correct harness-agnostic place for per-turn usage. Limits are resolved by a
core-internal non-blocking SWR helper (src/infra/provider-usage.limits.ts) and
attached to the snapshot — no new plugin-SDK accessor. All fields optional/additive.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(hooks): make usageState limits credential-aware + document best-effort delivery

Addresses review feedback on #89629.

1) Provider-limit resolution no longer defaults to OAuth. getProviderUsageLimits
   and getProviderUsageLimitsCached resolved with `credentialType ?? "oauth"`, and
   the agent-runner snapshot call passed no credential type, so an api-key OpenAI
   turn could borrow cached OAuth/ChatGPT usage windows. Drop the "oauth" default
   (missing credential type => no OpenAI usage provider) and thread the turn's
   authMode through at the call site. Adds provider-usage.limits.test.ts covering
   api-key/no-credential (no fetch), oauth/token (resolves), and non-OpenAI.

2) usageState is documented as best-effort, present only on live dispatcher
   delivery. Routed durable and recovered queue replays re-run this hook as a
   stateless transform over the original payload (see QueuedDeliveryPayload); a
   point-in-time usage snapshot is not stateless and would replay stale after a
   restart, so it is intentionally omitted there. Consumers must treat the field
   as optional.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(usage): map auth mechanism to usage-credential type for provider limits

requestShaping.authMode is the auth *mechanism* (e.g. "auth-profile" for a
configured auth profile), not the credential *type* resolveUsageProviderId
expects. Gating limits on it === "oauth"/"token" dropped 📊 for legit OAuth
(profile-based) turns. Map it: api-key/aws-sdk -> no usage provider (cannot
borrow cached oauth windows); oauth/token/auth-profile/absent -> usage-eligible,
with the real credential re-checked at fetch time.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(usage): thread real context occupancy + last-call usage into usageState

context.used_tokens / pct_used were derived from the snapshot's aggregate
prompt total (cacheRead+cacheWrite+input). Over a multi-call tool-loop turn
that is the run AGGREGATE, overstating window occupancy (often past 100%) so a
footer's context gauge pins full while /status shows the true ~7%.

Add two optional fields to PluginHookReplyUsageState and populate them in the
reply path:
- contextUsedTokens: the final call's prompt size (agentMeta.promptTokens) =
  real end-of-turn occupancy, a point-in-time state, not the aggregate.
- lastUsage: the final model call's usage only (vs `usage`, the turn
  aggregate), so a footer can render the last exchange's i/o + cache.

Both optional and additive; consumers fall back to the aggregate when absent
(correct for single-call turns). Renderer consumption lands separately (#89835).

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(usage): don't resolve subscription windows for an absent auth signal

A missing per-turn authMode was mapped to "oauth", so an OpenAI api-key turn
that arrived without an explicit auth mechanism could resolve and display
ChatGPT subscription windows that aren't its own — served straight from the 60s
limits cache, which the "re-checked at fetch time" guard does not cover.

Treat a genuinely absent signal as non-eligible (same as api-key): no usage
provider resolves and the footer omits limit windows. Present mechanisms are
unchanged — oauth/auth-profile/token stay eligible, and only OpenAI is gated on
the credential type so other providers are unaffected. A real oauth/profile turn
always carries its mechanism; one arriving blank is an upstream tagging bug to
fix at the source.

Inverts the now-incorrect "absent => oauth-eligible" test into regression
coverage for the absent/api-key case.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(hooks): tighten reply usage state correlation

* fix(models): bound model browse discovery (#92247)

Bounds default model browsing to configured/read-only discovery while preserving explicit full-catalog browsing. Reuses prepared plugin metadata and auth state without triggering external CLI discovery on the picker hot path, while retaining provider normalization and canonical runtime aliases.

Verified with focused model tests, official OpenAI and Anthropic transport suites, fresh live tool calls for both providers, a full build, AWS check:changed, remote Docker OpenAI tools E2E, and green PR CI.

Fixes #91809.

Co-authored-by: samson1357924 <[email protected]>

* fix: require admin for HTTP model overrides

Co-authored-by: Peter Steinberger <[email protected]>

* fix(gateway): honor profile auth for SecretRef model entries (#90686)

Fixes #90685 by allowing models.list availability to use matching auth-profile credentials when provider config contains a non-env SecretRef, while preserving unavailable results for unresolved SecretRef-only providers.

Adds isolated regression coverage for file SecretRefs and secretref-managed provider markers.

Co-authored-by: Rohit <[email protected]>

* feat(usage): native templated /usage full renderer (retires the footer plugin)

Render the per-reply /usage full footer in core from a declarative template
(the openclaw.usageBar.v1 format) when messages.usageTemplate is set; fall back
to the built-in line otherwise. Ports the reference usage_bar.py engine to TS so
no external process is involved (the external surface is just template data).

- usage-bar/translator.ts: engine (verbs num/dur/pct/inv/alias/meter, segment
  forms text/when/map/each, output.surfaces, item_scales). Codepoint-correct
  glyph indexing; fail-open (empty render -> boring fallback).
- usage-bar/contract.ts: buildUsageContract (snapshot -> openclaw.usageLine.v1).
- usage-bar/template.ts: resolve from a path (mtime-cached) or inline object.
- agent-runner: capture the per-turn usage snapshot and render the template at
  the /usage full branch in place of the built-in line when configured.
- messages.usageTemplate config (string path | inline object) + strict schema.
- translator.test.ts: verb parity, segment forms, astral glyphs, e2e render.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* style(usage): satisfy oxfmt + oxlint for native renderer

Formatting (import order, indentation) and bracket-notation access for
reserved template keys (_aliases/_default) to satisfy no-underscore-dangle.
No behavior change.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(usage): clear type-aware oxlint in usage-bar translator

- no-misused-spread: use Array.from for code-point glyph split (same
  behavior, astral glyphs intact) instead of string spread.
- no-base-to-string: return the case glyph only when it is a string.
- no-unnecessary-type-assertion: drop redundant cast already narrowed
  by isObject.

No behavior change (render output byte-identical; tsgo clean).

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* feat(usage): add fixed:N decimal-format verb to usage-bar translator

num truncates sub-1000 values to integers, so monetary fields like
cost.turn_usd rendered as 0 (or with full float noise when piped raw).
Add a fixed:N verb that formats a number to N decimal pla…
@gbb-netizen

Copy link
Copy Markdown

@brokemac79
image
I do still get this on 2026.6.8. I'm using chatgpt-5.5 with codex. I'm not sure where the discrepancy is. I upgraded to 2026.6.8, ran openclaw sandbox recreate --all, ran /new, then asked it to do something that should use a built-in skill (in this case, check for unread emails, so it should use gog). It's still trying to read inaccessible directories. Is there some type of fuller reset I need to do? Or are you able to identify any way this might not still be working?

@brokemac79

Copy link
Copy Markdown
Contributor Author

@gbb-netizen id suggest going to the discord , the help channel, and Krill (bot that has full access to docs etc ). Explain to krill the issue refs and PR refs and the latest issue and screenshots. If it can't help with changing config or settings get it to draft a full new issue ticket for the team to pick up

@gbb-netizen

gbb-netizen commented Jun 16, 2026

Copy link
Copy Markdown

@gbb-netizen id suggest going to the discord , the help channel, and Krill (bot that has full access to docs etc ). Explain to krill the issue refs and PR refs and the latest issue and screenshots. If it can't help with changing config or settings get it to draft a full new issue ticket for the team to pick up

I think because docker sandbox skills with write access only ever worked briefly, it never picked up a userbase, so unfortunately as far as I know the docs have never actually reflected a known working state, and there's not users out there to ask what their config is. Right now my config remains what it was when I shared it before, so it's relatively minimal and basically just copied from the documentation. I'd love to try what your config was, if you're able to share. My openclaw.json doesn't specify the user home directory anywhere so it shouldn't be hardcoded somewhere unless there's insufficient reset of state.

There's also the hope that #37634 is eventually addressed, which is the reason you can't use the default mode for writable skills.

wangmiao0668000666 pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 17, 2026
@gbb-netizen

gbb-netizen commented Jun 17, 2026

Copy link
Copy Markdown

I wonder if any remaining issue is related to my use of Codex, I think there's some special pathing for Codex in sandboxes

That said, the bot itself still seems to think its workspace is /home/.../workspace etc etc instead of /workspace. So maybe there's just still a larger problem there.

Is there any use completely rerunning the docker sandbox building scripts?

image

@gbb-netizen

Copy link
Copy Markdown

At least the agent seems to think there's some wider issue:

image

@gbb-netizen

Copy link
Copy Markdown

Yeah, if I read the session file manually even the cwd in its context isn't correct

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants