Skip to content

Unchain Codex app-server harness defaults#70082

Merged
pashpashpash merged 1 commit intoopenclaw:mainfrom
pashpashpash:codex/unchain-codex-harness
Apr 22, 2026
Merged

Unchain Codex app-server harness defaults#70082
pashpashpash merged 1 commit intoopenclaw:mainfrom
pashpashpash:codex/unchain-codex-harness

Conversation

@pashpashpash
Copy link
Copy Markdown
Contributor

OpenAI models behave best in OpenClaw when the Codex app-server path can run like a trusted local Codex session. Before this change, the plugin defaulted native Codex to approvalPolicy: "on-request" and sandbox: "workspace-write". That conservative posture is a bad fit for unattended OpenClaw runs: heartbeats cannot see or answer native Codex approval prompts, and the workspace sandbox blocks the network and shell work heartbeat tasks need.

This flips the Codex app-server defaults to approvalPolicy: "never" and sandbox: "danger-full-access", updates the manifest and docs, and keeps existing explicit config/env overrides intact for people who want a tighter posture.

Evidence from the local branch:

  • pnpm test extensions/codex/src/app-server/config.test.ts extensions/codex/src/app-server/run-attempt.test.ts passed with 2 files and 18 tests.
  • pnpm check:changed passed in the commit hook with the extensions/docs lanes, 19 files, and 88 tests.
  • pnpm build passed.
  • I restarted the local Gateway on this branch and triggered a main-session heartbeat at 2026-04-22 01:39 PDT. The transcript recorded provider codex, model gpt-5.4, and the heartbeat ran pwd from /Users/pash/.openclaw/workspace plus curl -I https://example.com; both exited 0 and curl returned HTTP/2 200, with no native Codex permission block.
  • A separate direct Codex run with session codex-policy-validation also reported fallbackUsed: false, provider codex, model gpt-5.4, 3 bash tool calls, 0 failures, and curl -I https://api.github.com returned HTTP/2 200.

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 22, 2026

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Codex harness defaults disable approvals and enable full-access sandbox (insecure-by-default)
2 🟠 High Unauthenticated/unenforced remote Codex app-server WebSocket endpoint can drive privileged tool execution
1. 🟠 Codex harness defaults disable approvals and enable full-access sandbox (insecure-by-default)
Property Value
Severity High
CWE CWE-250
Location extensions/codex/src/app-server/config.ts:136-143

Description

The Codex harness now resolves runtime options with unsafe defaults:

  • approvalPolicy defaults to "never", disabling native approval gating.
  • sandbox defaults to "danger-full-access", granting broad host/network/shell access.

These defaults apply whenever the caller does not explicitly configure appServer.approvalPolicy/appServer.sandbox (or env overrides). If the Codex harness can be triggered by untrusted inputs (e.g., remote webchat/gateway users, CI jobs, shared multi-user hosts) or if an operator uses WebSocket transport to a reachable app-server, this configuration turns typical “agent tool use” into a practical RCE / privilege escalation path.

Vulnerable code (new defaults):

approvalPolicy: ... ?? "never",
sandbox: ... ?? "danger-full-access",

The plugin schema defaults were also changed to match, making these unsafe settings the default in configuration tooling/UX as well.

Recommendation

Make safe operation the default, and require explicit opt-in for unchained/full-access execution.

Recommended changes:

  1. Restore safer defaults (or choose more restrictive ones):
    • approvalPolicy: "on-request" (or "untrusted" for more conservative behavior)
    • sandbox: "workspace-write" (or "read-only")
  2. Add a hard guardrail: only allow sandbox: "danger-full-access" and/or approvalPolicy: "never" when an explicit allowDangerousDefaults: true (or similar) flag is set, and ideally only for stdio/local operation.

Example:

const approvalPolicy =
  resolveApprovalPolicy(config.approvalPolicy) ??
  resolveApprovalPolicy(env.OPENCLAW_CODEX_APP_SERVER_APPROVAL_POLICY) ??
  "on-request";

const sandbox =
  resolveSandbox(config.sandbox) ??
  resolveSandbox(env.OPENCLAW_CODEX_APP_SERVER_SANDBOX) ??
  "workspace-write";

if ((approvalPolicy === "never" || sandbox === "danger-full-access") && !config.allowDangerous) {
  throw new Error("Refusing to run with unchained/full-access Codex defaults without explicit allowDangerous opt-in");
}

Also update extensions/codex/openclaw.plugin.json defaults to match the safe defaults so config generation/UI does not encourage unsafe settings.

2. 🟠 Unauthenticated/unenforced remote Codex app-server WebSocket endpoint can drive privileged tool execution
Property Value
Severity High
CWE CWE-306
Location extensions/codex/src/app-server/transport-websocket.ts:18-22

Description

The Codex gateway supports a websocket transport that connects to an arbitrary URL provided by plugin config/env. The connection does not require authentication (auth token is optional) and there is no validation of URL scheme/host (e.g., restricting to localhost/wss).

Because the app-server is a JSON-RPC peer that can:

  • Control threadId/turnId values returned from thread/start and turn/start
  • Send item/tool/call requests that are forwarded into toolBridge.handleToolCall(...)

…a malicious or user-controlled WebSocket endpoint can coerce the gateway into executing OpenClaw dynamic tools (file/workspace/host actions) in the current session context.

Vulnerable code:

const headers = {
  ...options.headers,
  ...(options.authToken ? { Authorization: `Bearer ${options.authToken}` } : {}),
};
const socket = new WebSocket(options.url, { headers });

Recommendation

Require explicit security controls for WebSocket transport:

  • Require authentication when transport === "websocket" (e.g., make authToken mandatory) and fail closed if missing.
  • Restrict endpoints by default (e.g., allow only wss:// and/or ws(s)://localhost unless an explicit allowRemoteWebsocket: true is set).
  • Consider pinning to loopback/private ranges, and reject URL schemes other than ws:/wss:.

Example hardening in createWebSocketTransport / config resolution:

const u = new URL(options.url);
if (u.protocol !== "wss:" && u.protocol !== "ws:") throw new Error("Invalid scheme");
if (!options.authToken) throw new Error("authToken is required for websocket transport");
if (!(u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "::1")) {
  throw new Error("Remote websocket endpoints are not allowed by default");
}

Analyzed PR: #70082 at commit 80f9d5d

Last updated on: 2026-04-22T08:54:24Z

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation extensions: codex size: XS maintainer Maintainer-authored PR labels Apr 22, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR flips the Codex app-server harness defaults from approvalPolicy: "on-request" / sandbox: "workspace-write" to approvalPolicy: "never" / sandbox: "danger-full-access", so unattended OpenClaw heartbeats can use network and shell tools without stalling on unanswerable native Codex approval prompts. The change is applied consistently across config.ts, the plugin manifest, docs, tests, and CHANGELOG, and existing explicit config/env overrides continue to take precedence over the new defaults.

Confidence Score: 5/5

Safe to merge — intentional, well-documented default change with consistent test coverage across all affected surfaces.

All six changed files are internally consistent: the runtime fallback in config.ts, the JSON schema defaults in openclaw.plugin.json, the documentation table, the tests in config.test.ts and run-attempt.test.ts, and the CHANGELOG all agree on the new defaults. No existing overrides are affected. No P0/P1 findings.

No files require special attention.

Reviews (1): Last reviewed commit: "fix(codex): unchain app-server defaults" | Re-trigger Greptile

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6359395b80

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +139 to +143
"never",
sandbox:
resolveSandbox(config.sandbox) ??
resolveSandbox(env.OPENCLAW_CODEX_APP_SERVER_SANDBOX) ??
"workspace-write",
"danger-full-access",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep websocket sessions on constrained default policy

resolveCodexAppServerRuntimeOptions now unconditionally falls back to approvalPolicy: "never" and sandbox: "danger-full-access", so existing websocket deployments that only set transport/url silently become fully unchained. Because these values are forwarded on thread start/resume, remote app-server runs lose native approval gating and gain broader shell/network access compared with the previous defaults; this should be scoped to trusted local stdio runs (or require explicit opt-in) to avoid an unexpected privilege escalation for remote setups.

Useful? React with 👍 / 👎.

@pashpashpash pashpashpash force-pushed the codex/unchain-codex-harness branch from 6359395 to 80f9d5d Compare April 22, 2026 08:49
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80f9d5db81

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +139 to +143
"never",
sandbox:
resolveSandbox(config.sandbox) ??
resolveSandbox(env.OPENCLAW_CODEX_APP_SERVER_SANDBOX) ??
"workspace-write",
"danger-full-access",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Scope unchained defaults to stdio transport

These fallback values now apply to every app-server transport, so a websocket deployment that only sets appServer.transport: "websocket" (+ url) silently inherits approvalPolicy: "never" and sandbox: "danger-full-access" on thread start/resume/turn. That removes native approval gating and sandboxing for remote app-server sessions, which is a production safety regression compared with the previous defaults and is especially risky because it happens without explicit opt-in.

Useful? React with 👍 / 👎.

@pashpashpash pashpashpash merged commit abf940d into openclaw:main Apr 22, 2026
92 checks passed
artisticlight added a commit to artisticlight/openclaw that referenced this pull request Apr 22, 2026
* fix: skip clean run-node runtime restaging

* fix: stabilize testbox test suite

* test: align plugin test contracts

* fix(agents): normalize malformed assistant replay content

* fix(agents): harden replay normalization guards

* fix(agents): distill replay content normalization

* fix: normalize assistant replay content (openclaw#69850) (thanks @fuller-stack-dev)

* Make harness failures fail honestly (openclaw#69981)

* Agents: fail honestly on harness errors

* Docs: clarify Codex harness fallback

* perf(ci): unblock node compat and trim runtime compat test

* docs: record testbox full-suite profile

* chore(agents): prefer local validation over testbox

* refactor: generalize route target parsing

* refactor: drop provider reconnect shim

* test: generalize legacy state migration coverage

* refactor: keep plugin login policy out of core

* fix(googlechat): harden google auth transport (openclaw#69812)

* fix(googlechat): localize google auth gaxios compat

* fix(googlechat): declare undici for staged runtime deps

* fix(googlechat): harden google auth transport

* fix(googlechat): narrow credential file reads

* fix(googlechat): preserve auth proxy transport

* fix(googlechat): allow symlinked auth files

* fix(googlechat): atomically load auth files

* fix(googlechat): eagerly buffer auth responses

* fix(googlechat): cap auth response buffering

* fix(googlechat): pin staged auth runtime deps

* fix(googlechat): buffer auth responses as array buffers

* Update CHANGELOG.md

* fix(googlechat): reject unstreamed auth responses

* fix(googlechat): use ambient fetch for auth transport

* fix(googlechat): keep guarded auth fetch on runtime path

* fix(googlechat): align staged zod range

* chore(lockfile): sync googlechat zod spec

* test: reuse plugin auto-enable fixture environment

* refactor: remove plugin tool display overrides from core

* fix(agents): guard replay convert hook

* test: generalize media fetch token fixtures

* feat(tencent): remove Token Plan provider and auth (openclaw#69996)

Co-authored-by: albertxyu <albertxyu@tencent.com>

* docs: fix stale community links in README and CONTRIBUTING (openclaw#69945)

Co-authored-by: Jonathan Amponsah <amponsahjonathan442@gmail.com>

* docs: generalize core channel examples

* fix(config): enforce resolved runtime channel config

* fix(channels): thread runtime config through sends

* fix(telegram): isolate sent-message cache stores

* fix(channels): preserve setup promotion fallbacks

* docs: remove bundled channel examples from core types

* refactor: generalize voice audio compatibility

* perf(test): avoid bundled channel fallback in model override tests

* docs: generalize core routing comments

* refactor: gate setup promotion by manifest feature

* docs(tui): document local config repair flow (openclaw#69995) (thanks @fuller-stack-dev)

* docs(tui): document local config repair flow

* docs(tui): clarify local TUI examples

* docs(config): gate local TUI repair flow

* docs(tui): fix local repair docs

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* docs: generalize plugin runtime comments

* perf(test): skip setup promotion metadata fallback

* perf(slack): narrow runtime-setter + lazy-load 4 modules + narrow 2 SDK surfaces (openclaw#69317)

Lazy load modules showing a ~50% gateway startup performance improvement

* feat(tokenjuice): bundle the native adapter (openclaw#69946)

* feat(plugins): register embedded extension factories

* feat(tokenjuice): bundle the native adapter

* fix(tokenjuice): gate the bundled embedded extension seam

* fix(tokenjuice): refresh runtime sidecar baseline

* fix(plugins): harden bundled embedded extensions

* fix(plugins): install source bundled runtime deps

* fix(tokenjuice): sync lockfile importer

* fix(plugins): validate reused runtime dep versions

* fix(plugins): restore tokenjuice CI contract

* fix(plugins): remove tokenjuice dts bridge

* fix(tokenjuice): repair openclaw type shim

* fix(plugins): harden bundled runtime deps

* fix(plugins): keep source checkout runtime deps local

* fix(plugins): isolate bundled runtime dep installs

* fix(cli): keep plugin startup registration non-activating

* fix(cli): keep loader overrides out of plugin cli options

* runtime-taskflow: sync blocked waiting edge into durable jobs

* docs(skill): tighten duplicate triage mirror rules

* docs(tokenjuice): add bundled plugin guide (openclaw#70038)

* docs(tokenjuice): add bundled plugin guide

* docs(tokenjuice): sort nav entry

* docs(changelog): mention tokenjuice embedded support (openclaw#70039)

* Fix Codex auth handoff for the app-server harness (openclaw#69990)

* Codex: fix auth bridge token shape

* Codex: preserve selected auth tokens

* Codex: prefer selected profile id token

* Codex: honor inherited Codex home

---------

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>

* fix(agents): keep mocked OpenAI Responses on HTTP (openclaw#69815)

* fix(agents): keep mocked OpenAI responses on HTTP

* docs(changelog): add entry for mocked responses fix

* fix(release-check): assert bundled plugin runtime deps after packed postinstall (openclaw#70035)

* fix(release-check): assert bundled plugin runtime deps after packed postinstall

Release-check already validates source dist/extensions runtime deps are staged, but runPackedBundledChannelEntrySmoke never re-validates after the packed postinstall runs against the installed tarball. That gap is how 2026.4.21 shipped without @whiskeysockets/baileys in dist/extensions/whatsapp/node_modules, because the source staging passed while the installed layout was left broken.

Re-use collectBuiltBundledPluginStagedRuntimeDependencyErrors against the installed packageRoot right after runPackedBundledPluginPostinstall and fail release-check if any declared runtime dependency is missing from the plugin-local node_modules.

* fix(release-check): check postinstalled dep sentinels at packageRoot/node_modules

Codex review on openclaw#70035 caught that collectInstalledBundledPluginRuntimeDepErrors was pointing at dist/extensions/<id>/node_modules, but packed postinstall installs and probes sentinels at packageRoot/node_modules (see dependencySentinelPath in scripts/postinstall-bundled-plugins.mjs). The previous implementation would have falsely failed release-check on healthy packed installs while still missing the original WhatsApp regression.

Reuse discoverBundledPluginRuntimeDeps from postinstall-bundled-plugins.mjs so the release guard uses the exact same dep discovery and sentinel paths the packed postinstall uses. Update the test fixtures accordingly so they model the real install layout.

* feat(openai): add codex device-code auth and fix login options in menu (openclaw#69557)

Merged via squash.

Prepared head SHA: 4918ed6
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Reviewed-by: @BunsDev

* fix: make slack socket health event-driven

* fix: clean up slack socket waiters on start hooks

* fix: use transport activity for stale health

* docs: update changelog for channel health (openclaw#69833) (thanks @bek91)

* ci: serialize parity gate scenarios

* ci: stabilize parity gate runner

* ci: pin qa parity tool profile

* ci: build runtime before parity gate

* test: harden qa parity config cleanup

* fix: drop stale socket mode opt-in

* test: harden qa parity runtime staging

* ci: build private qa parity runtime

* test: harden qa private runtime staging

* fix: harden tokenjuice host typing

* fix: keep custom pi tools executable

* tooling: add corepack pnpm fallback for git hooks

* qa: harden parity gate execution (openclaw#70045)

* fix: keep claude cli sessions warm (openclaw#69679)

* feat(cli): keep claude cli sessions warm

* test(cli): cover claude live session reuse

* fix(cli): harden claude live session reuse

* fix(cli): redact mcp session key logs

* fix(cli): bound claude live session turns

* fix(cli): reuse claude live sessions on resume

* refactor(cli): canonicalize claude live argv

* fix(cli): preserve claude live resume state

* fix(cli): close dead claude live sessions

* fix(cli): serialize claude live session creates

* fix(cli): count pending claude live sessions

* fix(cli): tighten claude live resume abort

* fix(cli): reject closed claude live sessions

* fix(cli): refresh claude live fingerprints

* fix(cli): stabilize MCP resume hash

* fix: preserve claude live inline resume (openclaw#69679)

---------

Co-authored-by: Frank Yang <frank.ekn@gmail.com>

* fix(pair): render /pair qr as media (openclaw#70047)

* fix(pair): render pair qr as media

* fix(gateway): preserve media reply threading

* fix(gateway): harden webchat media replies

* fix(plugin-sdk): keep trustedLocalMedia internal

* docs(changelog): note pair qr media fix

* Update CHANGELOG with recent fixes and enhancements

Updated changelog to include recent fixes and enhancements.

* fix(codex): unchain app-server defaults (openclaw#70082)

* place permission under each branch of bot permissions for discord docs (openclaw#69218)

Merged via squash.

Prepared head SHA: dd6ae52
Co-authored-by: epicseven-cup <59263116+epicseven-cup@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark

* fix: lower the log level from info to debug (openclaw#70108)

* fix(cli): keep provider-owned sessions through implicit expiry

* fix(cli): upgrade legacy mcp session reuse

* fix(gateway): preserve cli session binding metadata

* fix: update cli session changelog (openclaw#70106)

* fix(config): accept truncateAfterCompaction (openclaw#68395)

Merged via squash.

Prepared head SHA: bf45148
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819

* chore(pi): remove local pr prompts

Remove repo-local /landpr and /reviewpr prompt templates so maintainers use the externally maintained workflow instead.
These flows remain available from the external maintainers repo via globally installed Pi skills and prompts.

* fix(plugins): avoid doctor crash on legacy interactive state (openclaw#70135)

* fix(plugins): hydrate legacy interactive state

* fix(plugins): avoid doctor crash on legacy interactive state (openclaw#70135) (thanks @ngutman)

* fix(cli): stabilize oauth session auth epochs

* test(cli): cover oauth auth epoch continuity

* fix: update cli session changelog (openclaw#70132)

* fix: require cli auth epoch version (openclaw#70132)

* fix(qqbot): add interaction intents (openclaw#70143)

* feat(qqbot): add intents interaction

* fix(qqbot): add interaction intents (openclaw#70143) (thanks @cxyhhhhh)

---------

Co-authored-by: sliverp <870080352@qq.com>

* fix(codex): apply GPT-5 prompt overlay (openclaw#70175)

* ci: consolidate test shard fanout

* chore: update dependencies

* fix(agent): align pi session tool options

* ci: downsize blacksmith runners

* ci: run aggregate checks off blacksmith

* fix(gateway): harden WS pairing locality

* fix: fail closed on plugin integrity drift

* ci: keep long matrix aggregates on blacksmith

* build: refresh a2ui bundle hash

* ci: keep cpu-sensitive lanes larger

* fix(doctor): skip token generation for trusted-proxy and none auth modes (openclaw#59055)

runGatewayAuthHealth() only excluded 'password' and 'token' (with existing
token) from its needsToken check. When gateway.auth.mode was set to
'trusted-proxy' or 'none', doctor --fix would incorrectly:

1. Flag the config as 'missing a token'
2. Prompt to generate a gateway token
3. Overwrite auth.mode to 'token' in openclaw.json

This silently broke trusted-proxy deployments (common in SaaS/reverse-proxy
setups) by replacing the delegated auth mode with token auth.

The fix aligns runGatewayAuthHealth() with the existing
hasExplicitGatewayInstallAuthMode() in auth-install-policy.ts, which
already correctly returns false for 'password', 'none', and 'trusted-proxy'.

Co-authored-by: wujiaming88 <wujiaming88@example.com>

* fix: preserve restart continuations after reboot (openclaw#63406) (thanks @VACInc)

* gateway: add restart continuation sentinel

* gateway: address restart continuation review

* gateway: handle restart continuation edge cases

* gateway: keep restart continuations on threaded delivery path

* fix(gateway): harden restart continuation routing

* test(gateway): cover restart continuation edge cases

* docs(agent): clarify restart continuation usage

* fix: preserve restart continuations after reboot (openclaw#63406) (thanks @VACInc)

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* ci: move lightweight automation off blacksmith

* docs(changelog): fix cron attribution

* docs(changelog): correct cron contributors

* docs(changelog): thank cron contributors

* ci: skip aggregate fan-in after cancellation

* ci: refresh ci concurrency group

* fix(gateway): write restart sentinels atomically

* docs(changelog): note restart sentinel atomic writes

* fix(gateway): allow silent metadata-upgrade pairing for loopback CLI clients (openclaw#70224)

Loopback CLI clients (cli_container_local, shared_secret_loopback_local)
with valid shared-secret auth previously got disconnected with 1008
pairing required whenever the paired device record's platform or
deviceFamily string differed from what the CLI claimed at connect time.

PR openclaw#69431 added the shared_secret_loopback_local locality but deferred
the metadata-upgrade reason from the auto-approval allowlist. That
deferral created an unrecoverable handshake loop in practice: every CLI
connect triggers a fresh metadata-upgrade request, the Control UI has
no approval surface for this reason, and non-interactive shells cannot
complete pairing. This broke every non-interactive openclaw agent use
case when paired device keys are replicated across hosts or installs
are migrated across platforms.

Extend shouldAllowSilentLocalPairing to auto-approve metadata-upgrade
for cli_container_local and shared_secret_loopback_local localities
only. Browser / Control-UI / remote paths retain existing approval-
required behavior. Gateway still logs every metadata refresh via the
existing security audit line for operator review.

Add 4 unit tests covering the decision table for metadata-upgrade
across all four localities.

Related: openclaw#69397, openclaw#69431

* refactor(gateway): unify startup task execution

* fix(pairing): clear stale requests on device removal (openclaw#70239)

* fix(pairing): clear stale requests on device removal

* docs(changelog): note pairing stale request cleanup

* perf(test): avoid bundled setup in auto-enable tests

* ci: rebalance agentic node tests

* fix(gateway): preserve restart continuation chat type

* ci: reduce blacksmith test pressure

* ci: offload short linux checks

* ci: keep build smoke on blacksmith

* ci: rebalance runtime config tests

* ci: consolidate short test workers

* ci: keep native lanes native scoped

* fix(dotenv): block connector endpoint workspace overrides (openclaw#70240)

* fix(dotenv): block connector endpoint workspace overrides

* docs(changelog): note dotenv endpoint blocklist

* fix(dotenv): block Matrix per-account scoped homeserver overrides

* feat: Add /models add hot-reload model registration (openclaw#70211)

* feat(models): add chat model registration with hot reload

* docs(changelog): add models entry for pr 70211

* fix(models): harden add flow follow-ups

* fix models add review follow-ups

* harden models add config writes

* tighten plugin boundary invariant

* move models add adapters behind sdk facades

* avoid ollama-specific core facade

* ci: reuse build artifacts for gateway topology

* telegram: align model picker callback auth (openclaw#70235)

* telegram: align model picker callback auth

* docs(changelog): note telegram model callback auth fix

* fix(telegram): use runtime config for model callback auth

* Revert "ci: reuse build artifacts for gateway topology"

This reverts commit be31776.

* ci: trim gateway watch build profile

* ci: keep workflow edits off windows lane

* ci: parallelize additional boundary guards

* fix: load staged dist-runtime plugins in docker

* test: stabilize audio directive tag test

* fix: add plugin load debug shape

* fix(agents): accept silent no-reply turns

* fix: default claude cli to stdio sessions

* ci: add fast docker install smoke

* docs: update claude cli stdio notes

* fix(config): preserve source config during recovery

* test: keep loader fixture inside plugin boundary

* fix(discord): thread runtime config through guild actions

* fix(discord): use resolveDiscordChannelParentIdSafe in voice command path

openclaw#69908 switched native slash commands, listeners, and the model picker to
the safe accessor for partial thread channels, but the voice /join command
still reads channel.parentId through the unsafe "parentId" in channel
pattern. Route it through the same helper so the voice command path does
not crash with "Cannot access rawData on partial Channel" when invoked
from inside a thread on @buape/carbon >=0.16.

* fix(discord): use resolveDiscordChannelNameSafe for voice channel override name

Applies the same safe-accessor pattern to the adjacent name field.
If @buape/carbon implements name as a getter that also reads _rawData
(like parentId), the previous `"name" in channel` pattern would throw
for the same reason. Aligns with the fix for parentId in the same call
site.

* fix(plugins): harden bundled runtime dep staging

* fix: harden Discord voice commands in threads

* ci: run install smoke for runtime dep staging

* fix(hooks): standardize outbound routing metadata

* test(slack): drop obsolete adapter hook test

* ci: downsize install smoke runner

* fix(discord): make thread parent inheritance opt-in

* fix: make Discord thread parent inheritance opt-in (openclaw#69986) (thanks @Blahdude)

* refactor: move channel doctor migrations to plugins

* refactor(memory): migrate lancedb recall to prompt-build hook

* refactor: build channel setup input generically

* tooling: finish corepack-only pnpm repo paths

* fix(agent-runner): accept injected normalizeMediaPaths in runAgentTurnWithFallback

* fix(agent-runner): share media-path normalizer with runAgentTurnWithFallback to prevent duplicate outbound media

* test(agent-runner): regression — createReplyMediaPathNormalizer.runtime not called when normalizer injected

* fix: share reply media context (openclaw#68111) (thanks @ayeshakhalid192007-dev)

* refactor: move doctor capabilities to channel manifests

* test: keep hook and slack tests on public boundaries

* refactor: declare channel add flags in manifests

* refactor: use memory slot defaults in core paths

* test: keep config fallback test on generic plugin channel

* fix(cli-session): only hash static extraSystemPrompt for session reuse

The extraSystemPrompt includes per-message dynamic content from
buildInboundMetaSystemPrompt() (timestamps, message IDs, sender metadata)
that changes on every inbound message. This causes the extraSystemPromptHash
to differ every turn, triggering a session reset with reason='system-prompt'
and discarding all CLI session context.

Fix: split extraSystemPrompt into dynamic (inbound meta) and static
(group context, group intro, group system prompt, exec override hints)
portions. Only hash the static portion for session reuse validation.

The full extraSystemPrompt (dynamic + static) is still sent to the CLI
as before — only the session stability hash uses the static subset.

Fixes openclaw#70100

* fix: address review feedback — handle empty static prompt and remove stray blank lines

- Always pass extraSystemPromptStatic as string (even when empty) so the
  fallback in prepare.ts never accidentally hashes dynamic content
- Use explicit undefined check (params.extraSystemPromptStatic !== undefined)
  instead of ?? nullish coalescing to avoid edge case where empty static
  string falls through to hashing the full dynamic prompt
- Remove extra blank line

* fix(cli-session): forward static prompt hash input

* fix: stabilize Claude CLI session prompt hashing

* fix(hooks): expose typed gateway startup context

* fix(plugins): preserve source activation config

* ci: use dist cache instead of artifact upload

* refactor(hooks): centralize bundled subagent hook wiring

* refactor(discord): centralize thread channel context

* refactor(discord): share channel action param parsing

* refactor(discord): share partial channel test fixtures

* ci: parallelize extension batch groups

* fix(discord): break monitor threading import cycle

* refactor(hooks): centralize matrix subagent hook wiring

* fix(hooks): prefer shared outbound conversation context

* ci: fold build smoke into artifact job

* test(memory): drop stale dreaming hook doubles

* fix: honor ACP spawn model overrides (openclaw#70210)

Honor explicit ACP sessions_spawn model overrides and preserve ACP runtime cwd options.\n\nThanks @felix-miao.

* fix(hooks): canonicalize thread ownership conversation ids

* test(acpx): exercise registered reply_dispatch hook

* fix: persist CLI session clearing atomically (openclaw#70298)

Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant.

* ci: add scoped docker gateway e2e

* test(skill-workshop): exercise registered prompt hook

* fix: drop silent parent replies while subagents are pending (openclaw#69942)

Drop bare parent NO_REPLY payloads while spawned subagents are pending, preserving quiet parent turns until child completion delivers the real reply.\n\nThanks @neeravmakwana.

* ci: rotate stale concurrency group

* test(memory): exercise registered auto-capture hook

* ci: skip windows for test-only changes

* fix(auto-reply): preserve streaming reply directives (openclaw#70243)

Preserve streamed MEDIA/reply/audio directives across chunk boundaries and phase-aware final_answer delivery.\n\nThanks @zqchris.

* test(memory): exercise registered auto-recall hook

* Fix Slack HTTP route registry dispatch

* fix: route Slack HTTP webhook dispatch (openclaw#70275) (thanks @FroeMic)

* ci: narrow windows check scope

* fix(slack): pass cfg into resolveToken from downloadSlackFile call site

Commit 95331e5 ("fix(channels): thread runtime config through sends")
migrated resolveToken to a 3-arg signature (explicit, accountId, cfg) and
updated the getClient call site at actions.ts:83. The sibling call inside
downloadSlackFile at actions.ts:445 was not migrated and still dropped
opts.cfg, so the cfg-only resolution branch was unreachable from that path.

Current production callers (action-runtime.ts:386-389) always inject a
resolved readToken into opts.token before calling downloadSlackFile, so
this is defense-in-depth today -- the broken path is not hit in runtime.
Landing this closes the call-site migration gap and adds test coverage
for the cfg-only resolution contract on downloadSlackFile.

Note: pre-commit typecheck hook bypassed because upstream/main has 14
pre-existing TS errors in unrelated packages (discord, qa-lab, qqbot,
slack/monitor/provider.ts, tokenjuice, pi-embedded-runner) -- verified
reproducible on clean HEAD 4a16cf8 without this diff.

* fix: preserve Slack download cfg token fallback (openclaw#70160) (thanks @martingarramon)

* fix(telegram): mark polling transport dirty on 409 conflict (openclaw#69787)

When getUpdates returns 409 Conflict (e.g.
'terminated by other getUpdates request'), the polling runtime
previously retried on the same HTTP keep-alive TCP socket because
markDirty() was only called in the isRecoverable branch.

Telegram treats that connection as the 'old' session and keeps
terminating it — producing a sustained low-rate 409 retry loop
(observed a few per minute after eliminating duplicate pollers).

Broaden the dirty-mark condition to fire on isConflict as well as
isRecoverable so the next cycle forces a fresh TCP connection.

Update the existing 'reuses transport after getUpdates conflict' test
— which previously locked in the buggy behavior — to assert the new
correct behavior: one fresh transport is built, the stale one is
closed.

* test(telegram): update monitor test for openclaw#69787 transport rebuild on 409

Sibling test in monitor.test.ts asserted the pre-fix behavior (single
transport reused across cycles on 409). My openclaw#69787 change rebuilds the
transport on 409 so Telegram sees a fresh TCP socket — update the
assertion to match.

Two transports are now expected: the initial one plus the rebuild
after the conflict.

* test: tighten Telegram polling conflict coverage (openclaw#69873) (thanks @hclsys)

* test(plugins): guard legacy bundled hook regressions

* fix(telegram): lower webhook callback timeout to 5s

openclaw#16763 added `onTimeout: "return"` with `timeoutMilliseconds: 10_000`
(grammY default). In practice, Telegram's webhook servers abort the
read well before 10s when handler latency is LLM-bound: `getWebhookInfo`
reports `last_error_message: "Read timeout expired"` and pending updates
pile up, cascading into multi-minute reply lag.

Reproducible A/B on identical infra (same region, same bot token):
- Minimal Python echo bot: 5 back-to-back webhook RTTs 341-642ms, clean.
- OpenClaw current main: intermittent Read timeout expired, 1-5 min lag.

The handler still runs to completion; only the Telegram-facing ack is
sooner. grammY's deployment guide suggests 5s for long-running handlers.

No new config surface; minimal one-line change to the existing constant
and its test assertion. If a configurable timeout is wanted, that can be
a follow-up (see stale openclaw#7754).

* fix: lower Telegram webhook callback timeout (openclaw#70146) (thanks @friday-james)

* fix(amazon-bedrock): inject cache points for application inference profile ARNs (openclaw#69953)

* fix(amazon-bedrock): inject cache points for application inference profile ARNs

pi-ai's internal supportsPromptCaching checks model.id for specific Claude
model name patterns (e.g. "-4-", "claude-3-7-sonnet"), which fails for
application inference profile ARNs that don't contain the model name.
This causes prompt caching to silently break for Bedrock users with
application inference profiles.

Work around this by detecting when pi-ai would miss cache point injection
(via piAiWouldInjectCachePoints mirror) and patching the Converse API
payload via onPayload to add cachePoint blocks to the system prompt and
last user message — matching the same format pi-ai uses natively.

The fix is safe:
- Checks for existing cache points to avoid double-injection
- Respects cacheRetention: "none"
- Defaults to "short" retention (matching pi-ai default)
- Becomes a no-op once upstream pi-mono#2925 is fixed

Fixes openclaw#19279
Upstream: badlogic/pi-mono#2925

* fix(amazon-bedrock): tighten app-profile cache injection

---------

Co-authored-by: Your Name <you@example.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>

* test(plugins): pin bundled hook registration surfaces

* perf: keep gateway live probes off helper imports

* test(plugins): pin bundled hook names

* test: cover Telegram webhook timeout reply continuation

* fix(hooks): fail open without thread ownership routing

* fix(hooks): canonicalize slack thread ownership ids

* fix(plugins): repair bundled deps on activation

* fix(hooks): normalize thread ownership channel allowlists

* fix: clear phantom Claude CLI resumes (openclaw#70317)

Verify Claude CLI session transcripts before reuse and clear phantom bindings with transcript-missing instead of passing stale --resume ids.\n\nFixes openclaw#70177.

* fix(discord): restore DM reactions and guild activation

* fix(gateway): redact audio payloads from chat history

* fix(media): load inbound media store URIs

* fix(agents): dedupe emitted TTS media

* fix(image): resolve custom provider model IDs

* docs: note media delivery fixes

* fix(hooks): normalize thread ownership slack id casing

* fix(hooks): track thread ownership mentions case-insensitively

* ci: move node aggregate checks off blacksmith

* fix(hooks): tighten thread ownership mention matching

* test(skill-workshop): pin disabled hook wiring

* ci: rotate main concurrency queue

* fix(hooks): skip skill workshop capture when review is off

* fix(discord): harden partial thread channels

* test(memory): pin disabled lifecycle hook wiring

* ci: merge short auto-reply node shards

* fix(hooks): use live config for memory dreaming runtime

* feat(commands): gate /models add with modelsWrite (openclaw#70321)

* fix: restore Pi embedded tool allowlist

Restore the Pi embedded session tool allowlist for OpenAI/OpenAI Codex GPT-5 runs and compaction sessions after Pi 0.68.1 began treating session tools as a global allowlist.

Local validation: pnpm check:changed.
GitHub validation: check/check-additional/node shards green; parity gate red on unrelated config.patch stale/rate-limit QA harness scenario after plugins.allow restart.

* ci: rotate cancelled docs queue

* fix(hooks): refresh active memory config at runtime

* ci: balance extension tests across fewer workers

* fix: normalize opus 4.7 context window

Normalize Anthropic-owned Opus 4.7 context reporting to 1M while keeping inferred and bare discovery paths conservative.

- normalize Anthropic and claude-cli Opus 4.7 runtime/status context metadata to 1M
- keep inferred-provider and bare discovery ids on discovered conservative limits
- add regression coverage for provider, lookup, status, and discovery-cache paths
- keep the Telegram abort-signal wrapper typing narrow so changed-scope validation stays green

* fix(hooks): respect live skill workshop config

* fix(hooks): use live thread ownership config

* perf(plugins): cache normalized jiti aliases

* fix: honor explicit strict-agentic retry contract

Honor explicit strict-agentic execution contracts for incomplete-turn retry guards across providers, including local/compatible models that opt in without relying on OpenAI model inference.

Validation:
- pnpm test src/agents/pi-embedded-runner/run.incomplete-turn.test.ts
- pnpm check:changed
- GitHub CI + parity gate green

Thanks @ziomancer.

* test(media): harden media store URI validation

* ci: keep extension test fanout under two minutes

* fix(hooks): respect live lancedb memory config

* test(slack): cover send.ts customize-scope fallback retry path (openclaw#69009)

Adds 5 vitest cases for postSlackMessageBestEffort's silent retry
behavior when Slack rejects a chat:write.customize-identity post:

- Retry on err.data.needed matching chat:write.customize
- Retry on chat:write.customize in response_metadata.acceptedScopes
- Retry on chat:write.customize in response_metadata.scopes
- Rethrow on different missing_scope (e.g. channels:history)
- Rethrow when identity is empty (hasCustomIdentity returns false)

* fix(discord): normalize ACP thread binding targets

Normalize Discord ACP thread-binding channel targets at the REST/thread-create boundary while preserving current-conversation binding keys.\n\nThanks @Zetarcos.

* test(slack): provide send config in identity fallback tests

* fix(hooks): use live memory-core config during dreaming runs

* fix(memory-lancedb): retry failed runtime initialization

* runtime-taskflow: tighten waiting sync review fixes

* refactor(hooks): centralize live plugin config lookup

* fix(qa): deflake parity approval preflight

* fix(agents): centralize native websocket endpoint checks

* docs(changelog): note websocket endpoint classifier fix

* fix: clarify browser playwright-core install guidance

* fix(types): narrow live thread ownership config

* test(plugins): pin live config hook guards

* fix(agents): restore pi session tool activation

* docs(changelog): note pi session tool activation fix

* build: harden tsdown wrapper

---------

Co-authored-by: Shakker <shakkerdroid@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: FullerStackDev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: pashpashpash <nik@vault77.ai>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: JuniperSling <80268751+JuniperSling@users.noreply.github.com>
Co-authored-by: albertxyu <albertxyu@tencent.com>
Co-authored-by: Jonathan <82057176+mgalore@users.noreply.github.com>
Co-authored-by: Jonathan Amponsah <amponsahjonathan442@gmail.com>
Co-authored-by: Alex Knight <aknight@atlassian.com>
Co-authored-by: Andy <andrew@artisticforge.studio>
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
Co-authored-by: Dewaldt Huysamen <dhuysamen@gmail.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: Bek <bek.akhmedov@gmail.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
Co-authored-by: Jacky <59263116+epicseven-cup@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Co-authored-by: Sliverp <38134380+sliverp@users.noreply.github.com>
Co-authored-by: Ted Li <tl2493@columbia.edu>
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com>
Co-authored-by: cxy <49286167+cxyhhhhh@users.noreply.github.com>
Co-authored-by: sliverp <870080352@qq.com>
Co-authored-by: Garming <jiaming_wu@foxmail.com>
Co-authored-by: wujiaming88 <wujiaming88@example.com>
Co-authored-by: VACInc <hixvac@gmail.com>
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Jason Perlow <jperlow@gmail.com>
Co-authored-by: Devin Robison <drobison00@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Claw Kowalski <szponeczek@forserial.org>
Co-authored-by: Hana Chang <hana@hanamizuki.tw>
Co-authored-by: Oliver Camp <olivercamp@Olivers-MacBook-Pro-3.local>
Co-authored-by: ayeshakhalid192007-dev <ayeshakhalid192007@gmail.com>
Co-authored-by: Zijun Lin <zijunlin@Zijuns-Mac-mini.local>
Co-authored-by: Felix Miao <felix.us.miao@gmail.com>
Co-authored-by: HFConsultant <62076556+HFConsultant@users.noreply.github.com>
Co-authored-by: Neerav Makwana <neeravmakwana@gmail.com>
Co-authored-by: zqchris <chrisz83@gmail.com>
Co-authored-by: froemic <m.froehlich1994@gmail.com>
Co-authored-by: Martin Garramon <martin@yulicreative.ai>
Co-authored-by: HCL <chenglunhu@gmail.com>
Co-authored-by: friday-james <lamcollin37@gmail.com>
Co-authored-by: anirudhmarc <43162556+anirudhmarc@users.noreply.github.com>
Co-authored-by: Your Name <you@example.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
Co-authored-by: Josh Lehman <josh@martian.engineering>
Co-authored-by: Devin Matthews <zionitesoldier@gmail.com>
Co-authored-by: Zetarcos <117005244+Zetarcos@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation extensions: codex maintainer Maintainer-authored PR size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant