fix(gateway): classify loopback shared-secret clients as local for pairing#69431
Conversation
Greptile SummaryThis PR adds a new Confidence Score: 5/5Safe to merge — the fix is correctly scoped, the gate requires prior sharedAuthOk authentication, and 19 unit tests cover all decision paths including negative cases. The implementation is a minimal, well-understood extension to an existing pattern. The new gate function mirrors isCliContainerLocalEquivalent minus the CLI client ID check, all five security conditions are preserved, ordering in resolvePairingLocality is correct, and the tests cover the positive path, five negative cases, silent-pairing behavior, and CLI precedence. No P0/P1 findings. No files require special attention. Reviews (1): Last reviewed commit: "test(gateway): assert cli_container_loca..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23b26be731
ℹ️ 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".
| authMethod: params.authMethod, | ||
| }) | ||
| ) { | ||
| return "shared_secret_loopback_local"; |
There was a problem hiding this comment.
Handle scope-upgrade for shared_secret_loopback_local
Returning "shared_secret_loopback_local" here does not actually unblock the scope-upgrade case this commit targets: in src/gateway/server/ws-connection/message-handler.ts:937-940, requestDevicePairing is still forced to silent: false whenever reason === "scope-upgrade", so these loopback shared-secret clients will still get pairing required and disconnect on scope upgrades. This means NODE_HOST/TUI scope-upgrade flows remain broken despite the new locality classification.
Useful? React with 👍 / 👎.
Good catch on the At silent: reason === "scope-upgrade" ? false : allowSilentLocalPairing || allowSilentBootstrapPairingThis hardcodes What this PR does fix for NODE_HOST/TUI/GATEWAY_CLIENT:
The |
|
Makes sense — loopback + shared-secret should be treated as local regardless of how |
Good question — no, by design. The gate function return (
params.sharedAuthOk &&
usesSharedSecretAuth &&
!params.hasProxyHeaders && // ← Rejects if X-Forwarded-For present
!params.hasBrowserOriginHeader &&
isLoopbackAddress(params.remoteAddress) && // ← Requires 127.0.0.1/::1
isPrivateOrLoopbackHost(resolveHostName(params.requestHost))
);If a reverse proxy rewrites the connection:
This is intentional security: proxy headers indicate the connection didn't originate directly from loopback. A remote attacker behind a proxy shouldn't get local-trust classification just by sending a valid token. For legitimate internal tools behind a reverse proxy: The correct setup is:
Docker-compose / container networking case: If you have: services:
gateway:
ports: ["18789:18789"]
node-host:
# connects via service nameAnd
Solution: Have internal tools connect to
This matches existing behavior for |
23b26be to
0dcc08c
Compare
|
Landed after maintainer review and rebase.
Thanks @SARAMALI15792! |
…clients (#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 #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: #69397, #69431
…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
* 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>
Summary
remotebyresolvePairingLocality(), causingclose(1008, "pairing required")on scope-upgrade with no recovery path.PairingLocalityKindvariant"shared_secret_loopback_local"with gate functionisSharedSecretLoopbackLocalEquivalentinhandshake-auth-helpers.ts. Any loopback-origin connection with valid shared-secret (token/password), no proxy headers, and no browser origin header now classifies as local for pairing purposes.message-handler.ts, error messages, pending persistence, Control UI, or silent-pairing policy.shouldAllowSilentLocalPairingalready useslocality !== "remote"— the new kind passes automatically.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause (if applicable)
resolvePairingLocality()only recognized three local shapes:direct_local,browser_container_local(Control UI), andcli_container_local(CLI clients). Internal tools usingNODE_HOST,GATEWAY_CLIENT, orTUIclient IDs fell through to"remote"despite connecting from loopback with valid shared-secret auth.remotefallback.Regression Test Plan (if applicable)
src/gateway/server/ws-connection/handshake-auth-helpers.test.ts"shared_secret_loopback_local"(not"remote")User-visible / Behavior Changes
1008 "pairing required".Diagram (if applicable)
Security Impact (required)
NoNoNoNoNo— locality only affects the approval path, not authentication. Connections must still passsharedAuthOkbefore locality is evaluated.Repro + Verification
Environment
Steps
Expected
Actual
1008 "pairing required"Evidence
classifies non-CLI loopback + shared-secret clients as shared_secret_loopback_localkeeps non-CLI loopback clients remote without shared-secret auth(5 negative cases)allows silent scope-upgrade for shared_secret_loopback_localprefers cli_container_local over shared_secret_loopback_local for CLI clientsHuman Verification (required)
pnpm check/pnpm build(OOM on local machine — CI will validate). E2E with live gateway connection.Review Conversations
Compatibility / Migration
YesNoNoRisks and Mitigations
sharedAuthOk+ shared-secret auth method (token/password) + no proxy headers + no browser origin + loopback address + private/loopback host. Remote attackers cannot satisfy these conditions. The existing reason allowlist (not-paired, scope-upgrade, role-upgrade only — not metadata) is preserved unchanged.