* fix(cron): skip isError payloads when picking summary/delivery content
buildEmbeddedRunPayloads appends isError warnings as the last payload.
Three functions in helpers.ts iterate last-to-first and pick the error
over real agent output. Use two-pass selection: prefer non-error payloads,
fall back to error-only when no real content exists.
Fixes: pickSummaryFromPayloads, pickLastNonEmptyTextFromPayloads,
pickLastDeliverablePayload — all now accept and filter isError.
* Changelog: note cron payload isError filtering (#21454)
---------
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* cron: treat announce delivery failure as ok when agent execution succeeded
* fix: set delivered:false and error on announce delivery failure paths
* Changelog: note cron announce delivery status handling (#31082)
---------
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* fix(browser): include Chrome stderr and sandbox hint in CDP startup error (#29312)
When Chrome fails to start and CDP times out, the error message previously
contained no diagnostic information, making it impossible to determine why
Chrome couldn't start (e.g. missing --no-sandbox in containers, GPU issues,
shared memory errors).
This change:
- Collects Chrome's stderr output and includes up to 2000 chars in the error
- On Linux, if noSandbox is not set, appends a hint to try browser.noSandbox: true
Closes#29312
* chore(browser): format chrome startup diagnostics
* fix(browser): detach stderr listener after Chrome starts to prevent memory leak
Named the anonymous listener so it can be removed via proc.stderr.off()
once CDP is confirmed reachable. Also clears the stderrChunks array on
success so the buffered data is eligible for GC.
Fixes the unbounded memory growth reported in code review: a long-lived
Chrome process emitting periodic warnings would keep appending to
stderrChunks indefinitely since the listener was never removed.
Addresses review comment from chatgpt-codex-connector on PR #29355.
* changelog: note cdp startup diagnostics improvement
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: 派尼尔 <painier@openclaw.ai>
* feat(docker): add opt-in sandbox support for Docker deployments
Enable Docker-based sandbox isolation via OPENCLAW_SANDBOX=1 env var
in docker-setup.sh. This is a prerequisite for agents.defaults.sandbox
to function in any Docker deployment (self-hosted, Hostinger, DigitalOcean).
Changes:
- Dockerfile: add OPENCLAW_INSTALL_DOCKER_CLI build arg (~50MB, opt-in)
- docker-compose.yml: add commented-out docker.sock mount with docs
- docker-setup.sh: auto-detect Docker socket, inject mount, detect GID,
build sandbox image, configure sandbox defaults, add group_add
All changes are opt-in. Zero impact on existing deployments.
Usage: OPENCLAW_SANDBOX=1 ./docker-setup.sh
Closes#29933
Related: #7575, #7827, #28401, #10361, #12505, #28326
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address code review feedback on sandbox support
- Persist OPENCLAW_SANDBOX, DOCKER_GID, OPENCLAW_INSTALL_DOCKER_CLI
to .env via upsert_env so group_add survives re-runs
- Show config set errors instead of swallowing them silently;
report partial failure when sandbox config is incomplete
- Warn when Dockerfile.sandbox is missing but sandbox config
is still applied (sandbox image won't exist)
- Fix non-canonical whitespace in apt sources.list entry
by using printf instead of echo with line continuation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove `local` outside function and guard sandbox behind Docker CLI check
- Remove `local` keyword from top-level `sandbox_config_ok` assignment
which caused script exit under `set -euo pipefail` (bash `local`
outside a function is an error)
- Add Docker CLI prerequisite check for pre-built (non-local) images:
runs `docker --version` inside the container and skips sandbox setup
with a clear warning if the CLI is missing
- Split sandbox block so config is only applied after prerequisites pass
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: defer docker.sock mount until sandbox prerequisites pass
Move Docker socket mounting from the early setup phase (before image
build/pull) to a dedicated compose overlay created only after:
1. Docker CLI is verified inside the container image
2. /var/run/docker.sock exists on the host
Previously the socket was mounted optimistically at startup, leaving
the host Docker daemon exposed even when sandbox setup was later
skipped due to missing Docker CLI. Now the gateway starts without
the socket, and a docker-compose.sandbox.yml overlay is generated
only when all prerequisites pass. The gateway restart at the end of
sandbox setup picks up both the socket mount and sandbox config.
Also moves group_add from write_extra_compose() into the sandbox
overlay, keeping all sandbox-specific compose configuration together.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(docker): fix sandbox docs URL in setup output
* Docker: harden sandbox setup fallback behavior
* Tests: cover docker-setup sandbox edge paths
* Docker: roll back sandbox mode on partial config failure
* Tests: assert sandbox mode rollback on partial setup
* Docs: document Docker sandbox bootstrap env controls
* Changelog: credit Docker sandbox bootstrap hardening
* Update CHANGELOG.md
* Docker: verify Docker apt signing key fingerprint
* Docker: avoid sandbox overlay deps during policy writes
* Tests: assert no-deps sandbox rollback gateway recreate
* Docs: mention OPENCLAW_INSTALL_DOCKER_CLI in Docker env vars
---------
Co-authored-by: Jakub Karwowski <jakubkarwowski@Mac.lan>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* feat: add PDF analysis tool with native provider support
New `pdf` tool for analyzing PDF documents with model-powered analysis.
Architecture:
- Native PDF path: sends raw PDF bytes directly to providers that support
inline document input (Anthropic via DocumentBlockParam, Google Gemini
via inlineData with application/pdf MIME type)
- Extraction fallback: for providers without native PDF support, extracts
text via pdfjs-dist and rasterizes pages to images via @napi-rs/canvas,
then sends through the standard vision/text completion path
Key features:
- Single PDF (`pdf` param) or multiple PDFs (`pdfs` array, up to 10)
- Page range selection (`pages` param, e.g. "1-5", "1,3,7-9")
- Model override (`model` param) and file size limits (`maxBytesMb`)
- Auto-detects provider capability and falls back gracefully
- Same security patterns as image tool (SSRF guards, sandbox support,
local path roots, workspace-only policy)
Config (agents.defaults):
- pdfModel: primary/fallbacks (defaults to imageModel, then session model)
- pdfMaxBytesMb: max PDF file size (default: 10)
- pdfMaxPages: max pages to process (default: 20)
Model catalog:
- Extended ModelInputType to include "document" alongside "text"/"image"
- Added modelSupportsDocument() capability check
Files:
- src/agents/tools/pdf-tool.ts - main tool factory
- src/agents/tools/pdf-tool.helpers.ts - helpers (page range, config, etc.)
- src/agents/tools/pdf-native-providers.ts - direct API calls for Anthropic/Google
- src/agents/tools/pdf-tool.test.ts - 43 tests covering all paths
- Modified: model-catalog.ts, openclaw-tools.ts, config schema/types/labels/help
* fix: prepare pdf tool for merge (#31319) (thanks @tyler6204)
* fix(browser): skip port ownership check for remote CDP profiles
When a browser profile has a non-loopback cdpUrl (e.g. Browserless,
Kubernetes sidecar, or any external CDP service), the port-ownership
check incorrectly fires because we don't "own" the remote process.
This causes "Port is in use but not by openclaw" even though the
remote CDP service is working and reachable.
Guard the ownership error with !remoteCdp so remote profiles fall
through to the WebSocket retry/attach logic instead.
Fixes#15582
* fix: add TypeScript null guard for profileState.running
* chore(changelog): note remote CDP ownership fix credits
Refs #15582
* Update CHANGELOG.md
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(gateway): skip device pairing for local backend self-connections
When gateway.tls is enabled, sessions_spawn (and other internal
callGateway operations) creates a new WebSocket to the gateway.
The gateway treated this self-connection like any external client
and enforced device pairing, rejecting it with "pairing required"
(close code 1008). This made sub-agent spawning impossible when
TLS was enabled in Docker with bind: "lan".
Skip pairing for connections that are gateway-client self-connections
from localhost with valid shared auth (token/password). These are
internal backend calls (e.g. sessions_spawn, subagent-announce) that
already have valid credentials and connect from the same host.
Closes#30740
* gateway: tighten backend self-pair bypass guard
* tests: cover backend self-pairing local-vs-remote auth path
* changelog: add gateway tls pairing fix credit
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix: detect PID recycling in session write lock staleness check
The session lock uses isPidAlive() to determine if a lock holder is
still running. In containers, PID recycling can cause a different
process to inherit the same PID, making the lock appear valid when
the original holder is dead.
Record the process start time (field 22 of /proc/pid/stat) in the
lock file and compare it during staleness checks. If the PID is alive
but its start time differs from the recorded value, the lock is
treated as stale and reclaimed immediately.
Backward compatible: lock files without starttime are handled with
the existing PID-alive + age-based logic. Non-Linux platforms skip
the starttime check entirely (getProcessStartTime returns null).
* shared: harden pid starttime parsing
* sessions: validate lock pid/starttime payloads
* changelog: note recycled PID lock recovery fix
* changelog: credit hiroki and vincent on lock recovery fix
---------
Co-authored-by: HirokiKobayashi-R <hiroki@rhems-japan.co.jp>