fix: inter-session messages must not overwrite established external lastRoute#58013
Conversation
Greptile SummaryThis PR fixes a routing regression (issue #54441) where inter-session messages (sent via The core fix — adding an One P1 issue found:
Confidence Score: 4/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/gateway/server-methods/agent.ts
Line: 182-193
Comment:
**Dead-code guard — dedupe entry never cached for any error**
`error` is always created on line 174 as `errorShape(ErrorCodes.UNAVAILABLE, String(err))`, so `error.code` is always `ErrorCodes.UNAVAILABLE`. The condition `error.code !== ErrorCodes.UNAVAILABLE` is therefore **always `false`**, and the `setGatewayDedupeEntry` block inside will never execute.
The practical effect is a silent behavioral change: previously, all caught errors were written to the dedupe store; now **no** error is ever written. If a client resends the same idempotency key after a failed run, it will receive the earlier `"accepted"` entry from the store (set just before `dispatchAgentRunFromGateway`) rather than the actual error, hiding the failure from the caller.
If the intent is truly "never cache any error from this handler", remove the guard and the dead `setGatewayDedupeEntry` block entirely. If the intent is to leave room for caching non-transient errors in the future, the error would need to be shaped with a different code first.
```typescript
// Option A — never cache agent run errors (matches current effective behaviour):
params.respond(false, payload, error, {
runId: params.runId,
error: formatForLog(err),
});
// Option B — cache only non-transient errors (requires different error shapes upstream):
if (error.code !== ErrorCodes.UNAVAILABLE) {
setGatewayDedupeEntry({ ... });
}
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix: inter-session messages must not ove..." | Re-trigger Greptile |
| if (error.code !== ErrorCodes.UNAVAILABLE) { | ||
| setGatewayDedupeEntry({ | ||
| dedupe: params.context.dedupe, | ||
| key: `agent:${params.idempotencyKey}`, | ||
| entry: { | ||
| ts: Date.now(), | ||
| ok: false, | ||
| payload, | ||
| error, | ||
| }, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Dead-code guard — dedupe entry never cached for any error
error is always created on line 174 as errorShape(ErrorCodes.UNAVAILABLE, String(err)), so error.code is always ErrorCodes.UNAVAILABLE. The condition error.code !== ErrorCodes.UNAVAILABLE is therefore always false, and the setGatewayDedupeEntry block inside will never execute.
The practical effect is a silent behavioral change: previously, all caught errors were written to the dedupe store; now no error is ever written. If a client resends the same idempotency key after a failed run, it will receive the earlier "accepted" entry from the store (set just before dispatchAgentRunFromGateway) rather than the actual error, hiding the failure from the caller.
If the intent is truly "never cache any error from this handler", remove the guard and the dead setGatewayDedupeEntry block entirely. If the intent is to leave room for caching non-transient errors in the future, the error would need to be shaped with a different code first.
// Option A — never cache agent run errors (matches current effective behaviour):
params.respond(false, payload, error, {
runId: params.runId,
error: formatForLog(err),
});
// Option B — cache only non-transient errors (requires different error shapes upstream):
if (error.code !== ErrorCodes.UNAVAILABLE) {
setGatewayDedupeEntry({ ... });
}Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/server-methods/agent.ts
Line: 182-193
Comment:
**Dead-code guard — dedupe entry never cached for any error**
`error` is always created on line 174 as `errorShape(ErrorCodes.UNAVAILABLE, String(err))`, so `error.code` is always `ErrorCodes.UNAVAILABLE`. The condition `error.code !== ErrorCodes.UNAVAILABLE` is therefore **always `false`**, and the `setGatewayDedupeEntry` block inside will never execute.
The practical effect is a silent behavioral change: previously, all caught errors were written to the dedupe store; now **no** error is ever written. If a client resends the same idempotency key after a failed run, it will receive the earlier `"accepted"` entry from the store (set just before `dispatchAgentRunFromGateway`) rather than the actual error, hiding the failure from the caller.
If the intent is truly "never cache any error from this handler", remove the guard and the dead `setGatewayDedupeEntry` block entirely. If the intent is to leave room for caching non-transient errors in the future, the error would need to be shaped with a different code first.
```typescript
// Option A — never cache agent run errors (matches current effective behaviour):
params.respond(false, payload, error, {
runId: params.runId,
error: formatForLog(err),
});
// Option B — cache only non-transient errors (requires different error shapes upstream):
if (error.code !== ErrorCodes.UNAVAILABLE) {
setGatewayDedupeEntry({ ... });
}
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c71761db4
ℹ️ 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".
| if (params.isInterSession && hasEstablishedExternalRoute) { | ||
| return persistedChannel || sessionKeyChannelHint; |
There was a problem hiding this comment.
Prefer external hint over internal persisted channel
The inter-session fast path now returns persistedChannel || sessionKeyChannelHint, which can lock in an internal webchat value even when the session key already encodes an external route. For example, if persistedLastChannel is stale webchat but sessionKey is agent:...:discord:..., this branch returns webchat and bypasses the existing fallback logic that would have recovered to discord. This regresses recovery for already-contaminated sessions and keeps announce/delivery routing internal on inter-session turns.
Useful? React with 👍 / 👎.
| }); | ||
| // Do not cache transient (UNAVAILABLE) errors — retry callers must be | ||
| // able to re-execute the request once the underlying issue resolves. | ||
| if (error.code !== ErrorCodes.UNAVAILABLE) { |
There was a problem hiding this comment.
Overwrite accepted dedupe entry on run error
This condition is now unreachable because error is always created with ErrorCodes.UNAVAILABLE a few lines above, so failed runs never write a terminal dedupe entry. Since the request already stores an in-flight accepted entry earlier in agent, retries with the same idempotency key will keep hitting that stale accepted record instead of re-executing after a transient failure, which contradicts the intended retry behavior in the comment.
Useful? React with 👍 / 👎.
4c71761 to
cae0f84
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cae0f842fa
ℹ️ 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".
| @@ -437,10 +438,12 @@ export async function initSessionState(params: { | |||
| const baseEntry = !isNewSession && freshEntry ? entry : undefined; | |||
| // Track the originating channel/to for announce routing (subagent announce-back). | |||
| const originatingChannelRaw = ctx.OriginatingChannel as string | undefined; | |||
| const isInterSession = isInterSessionInputProvenance(ctx.InputProvenance); | |||
There was a problem hiding this comment.
Scope inter-session preservation to sessions_send webchat only
Computing isInterSession from provenance alone applies the new preservation path to all inter-session calls, not just sessions_send, but we also send inter-session runs with explicit external channel/to (for example subagent_announce in src/agents/subagent-announce-delivery.ts sets inputProvenance.kind = "inter_session" while passing real destination fields). With this flag set, the new guards in resolveLastChannelRaw/resolveLastToRaw keep the old persisted route whenever one already exists, so those external announce calls can no longer refresh stale lastChannel/lastTo to the current target route.
Useful? React with 👍 / 👎.
|
The two Windows CI failures ( This appears to be a pre-existing flaky test on Windows introduced with the flow/task system in v2026.3.31. I've opened issue #59103 to track it. Could a maintainer trigger a re-run of the failing Windows jobs, or confirm these failures can be treated as pre-existing flakiness for the purposes of this review? All Linux and macOS checks pass. |
|
Hi @greptile-apps — the
The dead-code guard in |
|
You're absolutely right, and I apologize for the noise. Comments should stay scoped to the actual PR diff. If there are issues in Your fix looks good - the inter-session guard is correctly placed and the test coverage validates the edge cases. Thanks for the clear explanation! |
|
Hi @chatgpt-codex-connector — thanks for the review. On the The When no external route exists yet ( So the guard is safe for |
|
To use Codex here, create a Codex account and connect to github. |
cae0f84 to
7872da2
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7872da223b
ℹ️ 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".
| // causing subsequent Discord (or other external) deliveries to be lost. | ||
| // See: https://github.com/openclaw/openclaw/issues/54441 | ||
| if (params.isInterSession && hasEstablishedExternalRoute) { | ||
| return persistedChannel || sessionKeyChannelHint; |
There was a problem hiding this comment.
Prefer external session hint over internal persisted channel
When isInterSession is true and an external route is inferred only from sessionKey, this early return prefers any truthy persistedChannel even if it is internal (webchat). In sessions already contaminated with lastChannel=webchat but an external key like agent:...:discord:..., inter-session turns now keep webchat and never recover to the external route, so follow-up announce/delivery can stay misrouted internally. Fresh evidence in this commit: the new return persistedChannel || sessionKeyChannelHint bypasses the later fallback that would have selected the external session-key hint.
Useful? React with 👍 / 👎.
| const baseEntry = !isNewSession && freshEntry ? entry : undefined; | ||
| // Track the originating channel/to for announce routing (subagent announce-back). | ||
| const originatingChannelRaw = ctx.OriginatingChannel as string | undefined; | ||
| const isInterSession = isInterSessionInputProvenance(ctx.InputProvenance); |
There was a problem hiding this comment.
Scope inter-session preservation to sessions_send only
This marks all inter_session provenance as route-preserving, but inter_session is also used by subagent announce paths that send explicit external channel/to targets; those flows need lastChannel/lastTo to refresh when the destination changes. With isInterSession derived only from provenance kind here, those calls now hit the new preserve guards and can retain stale persisted routes instead of the current explicit target. Fresh evidence in this commit: isInterSessionInputProvenance(ctx.InputProvenance) is now forwarded unconditionally into both route resolvers.
Useful? React with 👍 / 👎.
e98cd70 to
624352c
Compare
…astRoute Resolves openclaw#54441 Inter-session messages (sessions_send) always arrive with channel=webchat. When resolveLastChannelRaw / resolveLastToRaw processed these turns, they could overwrite a previously-established external lastChannel/lastTo (e.g. discord, telegram) with webchat routing ids, breaking delivery on all subsequent turns of that session. Fix: add isInterSession?: boolean to both resolver functions. When true and an external route is already persisted, return the persisted route unchanged. Behaviour for sessions with no external route established is preserved (inter-session can still set the initial route on brand-new sessions). Call site in session.ts passes isInterSessionInputProvenance(ctx.InputProvenance) to both resolvers. No other files are modified.
624352c to
820ea20
Compare
|
Merged via squash.
Thanks @duqaXxX! |
* release: mirror bundled channel deps at root (openclaw#63065) Merged via squash. Prepared head SHA: ac26799 Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com> Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com> Reviewed-by: @scoootscooob * fix(test): keep warn log capture under openclaw temp dir * revert: undo background alive review findings fix * feat: add qa character vibes eval * test: stabilize plugin boundary invariants * test: isolate agent gateway cli command mocks * test: skip duplicate package boundary wrapper in ci * test: fix postpublish verifier sidecar handling * test: keep status tests off live usage probes * auto-reply: type status auth overrides * plugins: read contract inventory from manifests * test: inline cli metadata channel fixture * ci: skip duplicate full extension shard * test: isolate discord directory live token env * test: keep followup runner memory mock complete * ci: split parallel full suite into leaf shards * test: guard loader fixtures against broad sdk imports * test: keep bundled channel entry smokes descriptor-only * ci: reduce full suite test parallelism * test: avoid bundled test api smokes in matrix and telegram * test: keep discord and irc entry smokes descriptor-only * test: keep web provider artifact coverage manifest-only * test: keep provider policy artifact coverage narrow * test: keep web provider artifact test in boundary * test: keep status message tests off auth auto-detection * status: avoid plugin lookup for direct channel model overrides * channels: fast-path direct model override matches * test: restore manifest-only web provider coverage * fix: allow blank TLS manual port default (openclaw#63134) (thanks @Tyler-RNG) * make port optional for TLS manual connections * fix: restrict manual blank-port fallback to tls * fix: allow blank TLS manual port default (openclaw#63134) (thanks @Tyler-RNG) --------- Co-authored-by: Ayaan Zaidi <hi@obviy.us> * test: fix full suite CI test isolation * fix: align LLM idle timeout policy * test: exercise models json file mode without provider discovery * test: keep shared dm policy contract off channel facades * test: keep web provider artifact test in boundary * test: keep kilocode provider tests on plugin-owned helpers * ci: restore sequential full suite tests * test: keep public artifact coverage on cheap boundaries * test: keep openclaw tools registration tests on a fast shell * test: keep bundled metadata sidecar scan inventory-only * docs(inferrs): fix Gemma model id from gg-hf-gg to google (openclaw#62586) * fix: harden bundled plugin dependency release checks * ci: isolate full suite leaf shards * test: keep openclaw tools registration policy pure * fix: support Codex CLI QA auth * feat: add QA character eval reports * docs: document QA character eval workflow * refactor: dedupe media generation tool helpers * refactor: dedupe internal helper glue * refactor: dedupe shared helper branches * refactor: dedupe browser navigation guard tests * refactor: dedupe config and subagent tests * refactor: dedupe test helpers and script warning filter * refactor: dedupe plugin test harnesses * refactor: dedupe media runtime test mocks * refactor: dedupe plugin metadata test helpers * refactor: dedupe firecrawl and directive helpers * refactor: dedupe exec defaults tests * refactor: dedupe approval runtime tests * refactor: dedupe matrix exec approval tests * refactor: dedupe telegram exec approval tests * refactor: dedupe doctor codex oauth tests * refactor: dedupe agent command test fixtures * refactor: dedupe embedding provider test fixtures * refactor: share html entity tool call decoding * fix: keep minimax provider mocks package-local * test: keep pdf and update-plan registration tests pure * test: keep model reasoning override coverage on merge helpers * fix: default OpenAI reasoning effort to high * test: keep kimi implicit provider tests on provider catalog * fix(build): prune stale bundled plugin node_modules * fix(build): address bundled plugin prune review * fix(build): honor postinstall disable flag * test: keep chutes implicit provider tests on provider catalog * fix(plugin-sdk): export channel plugin base * docs: reorder changelog entries * test: keep bundled web-search owner checks on public artifacts * fix(build): keep tsdown prune best-effort * test: trust gateway exec fixture node path * fix: keep runtime task test harness behind task seams * test: explain gateway exec fixture trust * Reply: surface OAuth reauth failures (openclaw#63217) Merged via squash. Prepared head SHA: 68b7ffd Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Reviewed-by: @mbelinky * test: make character eval scenario natural * feat: add character eval model options * test: keep pi fs workspace tests on fs tool factories * test: keep media runtime tests on same-directory provider mocks * fix(android): auto-resume pairing approval * fix(android): prefer bootstrap auth on qr pairing * fix(android): reset auth on new setup codes * fix(android): tighten pairing retry behavior * fix(android): prefer stored device auth after pairing * fix: restore android qr pairing flow (openclaw#63199) * fix(auto-reply): strip leading NO_REPLY tokens to prevent silent-reply leak (openclaw#63068) * fix(auto-reply): strip leading NO_REPLY tokens to prevent silent-reply leak * fix(auto-reply): preserve substantive NO_REPLY leading text * fix(agents): preserve ACP silent-prefix cumulative deltas * fix(auto-reply): harden silent-token streaming paths * fix(auto-reply): normalize glued silent tokens consistently --------- Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net> * fix(gateway): clear auto-fallback model override on session reset (openclaw#63155) * fix(gateway): clear auto-fallback model override on session reset When `persistFallbackCandidateSelection()` writes a fallback provider override with `authProfileOverrideSource: "auto"`, the override was incorrectly preserved across `/reset` and `/new` commands. This caused sessions to keep using the fallback provider even after the user changed the agent config primary provider, because the session store override takes precedence over the config default. Now the override fields (`providerOverride`, `modelOverride`, `authProfileOverride`, `authProfileOverrideSource`, `authProfileOverrideCompactionCount`) are only carried forward when `authProfileOverrideSource === "user"` (i.e. explicit `/model` command). System-driven overrides are dropped on reset so the session picks up the current config default. Introduced in cb0a752 ("fix: preserve reset session behavior config") * fix(gateway): preserve explicit reset model selection * fix(gateway): track reset model override source * fix(gateway): preserve legacy reset model overrides * docs(changelog): add session reset merge note --------- Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net> * test: stabilize ci test isolation * test: isolate volcengine byteplus auth resolver imports * fix: patch hono security advisories * fix: pass system prompt to codex cli * fix(plugins): prevent untrusted workspace plugins from hijacking bundled provider auth choices [AI] (openclaw#62368) * fix: address issue * fix: address review feedback * docs(changelog): add onboarding auth-choice guard entry * fix: address PR review feedback * fix: address PR review feedback * fix: address PR review feedback * fix: address PR review feedback * fix: address PR review feedback * fix: address PR review feedback * fix: address PR review feedback * fix: address PR review feedback --------- Co-authored-by: Devin Robison <drobison@nvidia.com> * test: isolate provider runtime test mocks * feat(plugins): support provider auth aliases * feat(memory): add grounded REM backfill lane (openclaw#63273) Merged via squash. Prepared head SHA: 4450f25 Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Reviewed-by: @mbelinky * feat(memory): harden grounded REM extraction (openclaw#63297) Merged via squash. Prepared head SHA: e188b7e Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Reviewed-by: @mbelinky * feat(ui): add dreaming diary controls and navigation (openclaw#63298) Merged via squash. Prepared head SHA: 0a2ae66 Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Reviewed-by: @mbelinky * chore(ui): refresh zh-TW control ui locale * chore(ui): refresh zh-CN control ui locale * chore(ui): refresh pt-BR control ui locale * chore(ui): refresh de control ui locale * chore(ui): refresh es control ui locale * chore(ui): refresh ko control ui locale * chore(ui): refresh ja-JP control ui locale * chore(ui): refresh fr control ui locale * docs(matrix): tighten setup and config guidance * chore(ui): refresh tr control ui locale * chore(ui): refresh uk control ui locale * chore(ui): refresh pl control ui locale * chore(ui): refresh id control ui locale * test: stabilize full-suite execution * fix(matrix): contain sync outage failures (openclaw#62779) Merged via squash. Prepared head SHA: 901bb76 Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras * Align remote node exec event system messages with untrusted handling (openclaw#62659) * fix(nodes): downgrade remote exec system events * docs(changelog): add remote node exec event entry --------- Co-authored-by: Devin Robison <drobison@nvidia.com> * test: reuse image generate tool imports * test: reuse followup runner imports * docs(config): tighten wording in reference * test: harden provider mock isolation * fix(memory): accept embedded dreaming heartbeat tokens * test: harden Parallels macOS smoke fallback * build: narrow plugin SDK declaration build * fix(dotenv): block workspace runtime env vars (openclaw#62660) * fix(dotenv): block workspace runtime env vars Co-authored-by: zsx <git@zsxsoft.com> * docs(changelog): add workspace dotenv runtime-control entry * fix(dotenv): block workspace gateway port override --------- Co-authored-by: zsx <git@zsxsoft.com> Co-authored-by: Devin Robison <drobison@nvidia.com> * build: stage nostr runtime dependencies * fix: load QA live provider overrides * feat: parallelize character eval runs * auth: avoid external cli sync on profile upsert * test(doctor): mock memory-core runtime seam * auth: persist explicit profile upserts directly * Matrix: report startup failures as errors * fix(browser): harden browser control override loading (openclaw#62663) * fix(browser): harden browser control overrides * fix(lint): prepare boundary artifacts for extension oxlint * docs(changelog): add browser override hardening entry * fix(lint): avoid duplicate boundary prep --------- Co-authored-by: Devin Robison <drobison@nvidia.com> Co-authored-by: Devin Robison <drobison00@users.noreply.github.com> * test: reuse exec directive reply imports * test: reuse verbose directive reply imports * fix(browser): re-check interaction-driven navigations (openclaw#63226) * fix(browser): guard interaction-driven navigations * fix(browser): avoid rechecking unchanged interaction urls * fix(browser): guard delayed interaction navigations * fix(browser): guard interaction-driven navigations for full action duration * fix(browser): avoid waiting on interaction grace timer * fix(browser): ignore same-document hash-only URL changes in navigation guard * fix(browser): dedupe interaction nav guards * fix(browser): guard same-URL reloads in interaction navigation listeners * docs(changelog): add interaction navigation guard entry * fix(browser): drop duplicate ssrfPolicy props * fix(browser): tighten interaction navigation guards --------- Co-authored-by: Devin Robison <drobison@nvidia.com> * test: move directive state coverage to pure tests * fix: enable thinking support for the ollama api (openclaw#62712) Merged via squash. Prepared head SHA: c0b9950 Co-authored-by: hoyyeva <63033505+hoyyeva@users.noreply.github.com> Co-authored-by: BruceMacD <5853428+BruceMacD@users.noreply.github.com> Reviewed-by: @BruceMacD * Slack: treat ACP block text as visible output (openclaw#62858) Merged via squash. Prepared head SHA: 14f202e Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras * fix: fail fast on qa live auth errors * fix: fail fast across qa scenario wait paths * test: cover qa scenario wait failure replies * fix: sanitize qa missing-key replies * test: cover sanitized qa missing-key replies * fix: align qa wait cursor semantics * test: cover mixed-traffic qa wait cursors * fix: classify curated qa missing-key replies * test: cover curated qa missing-key reply classification * fix: harden qa missing-key provider messages * test: cover unsafe qa missing-key providers * docs(changelog): add qa auth fail-fast entry (openclaw#63333) (thanks @shakkernerd) * fix(matrix/doctor): migrate legacy channels.matrix.dm.policy 'trusted' (fixes openclaw#62931) (openclaw#62942) Merged via squash. Prepared head SHA: d9f553b Co-authored-by: lukeboyett <46942646+lukeboyett@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras * Memory/dreaming: feed grounded backfill into short-term promotion (openclaw#63370) Merged via squash. Prepared head SHA: 5dfe246 Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Reviewed-by: @mbelinky * docs: update unreleased changelog * fix(gateway): classify dream diary actions * fix(memory): align dreaming status payloads * Memory/dreaming: harden grounded backfill follow-ups * test: reuse inline directive reply imports * Docs/memory: explain grounded backfill flows * fix(deps): patch basic-ftp advisory * test: move inline directive collisions to pure tests * Slack: dedupe partial streaming replies (openclaw#62859) Merged via squash. Prepared head SHA: cbecb50 Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras * test: replace exec directive e2e with pure coverage * fix(plugins): keep test helpers out of contract barrels (openclaw#63311) Merged via squash. Prepared head SHA: 769e90c Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com> Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com> Reviewed-by: @altaywtf * test: move cron heartbeat delivery coverage below full turns * fix: inter-session messages must not overwrite established external lastRoute (openclaw#58013) Merged via squash. Prepared head SHA: 820ea20 Co-authored-by: duqaXxX <12242811+duqaXxX@users.noreply.github.com> Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com> Reviewed-by: @jalehman * fix(gateway): suppress announce/reply skip chat leakage (openclaw#51739) Merged via squash. Prepared head SHA: 2f53f3b Co-authored-by: Pinghuachiu <9033138+Pinghuachiu@users.noreply.github.com> Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com> Reviewed-by: @jalehman * Slack: key turn-local dedupe by dispatch kind Scope Slack turn-local delivery dedupe by reply dispatch kind so identical tool and final payloads on the same thread do not collapse into one send. Expose the existing dispatcher kind on the public reply-runtime seam and cover the Slack tracker and preview-fallback paths with regression tests. * Dreaming: surface grounded scene lane (openclaw#63395) Merged via squash. Prepared head SHA: 0c7f586 Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Reviewed-by: @mbelinky * test: avoid runtime auth overlays in failure-state coverage * fix(ci): align ollama thinking expectations * chore(ui): refresh zh-CN control ui locale * chore(ui): refresh pt-BR control ui locale * chore(ui): refresh zh-TW control ui locale * chore(ui): refresh de control ui locale * test(docker): reduce e2e log noise * chore(ui): refresh es control ui locale * chore(ui): refresh fr control ui locale * chore(ui): refresh ja-JP control ui locale * chore(ui): refresh ko control ui locale * chore(ui): refresh uk control ui locale * chore(ui): refresh id control ui locale * chore(ui): refresh pl control ui locale * chore(ui): refresh tr control ui locale * fix: restore main ci * fix(ci): drop silent history before truncation * docs: reorder unreleased changelog * test(docker): quiet success-path e2e logs * style: sort session import * build: mirror bundled plugin runtime deps * plugins: load lightweight provider discovery entries * ci: narrow Windows node test lane * fix: filter provider auth aliases by plugin trust * fix: surface delayed browser navigation blocks * style: format memory and gateway touchups * Delete docs/plans directory Unused artifact * test: avoid remote ollama timeout in api-key preservation coverage * test: keep auth-choice default-model coverage on lightweight provider * test: keep undefined-token auth-choice coverage generic * fix: stabilize character eval and Qwen model routing * test: keep agent command tests off external auth overlays * fix openrouter model picker refs (openclaw#63416) * fix openrouter model picker refs Signed-off-by: sallyom <somalley@redhat.com> * test(ui): cover openrouter slash-id /model resolution --------- Signed-off-by: sallyom <somalley@redhat.com> Co-authored-by: Vignesh Natarajan <vignesh.natarajan92@gmail.com> * ci: stabilize macOS and transcript policy tests * test: keep cli-provider agent command tests off external auth overlays * chore(lint): clear extension lint regressions and add openclaw#63416 changelog * test: update modelstudio catalog contract sentinel * test: update character eval public panel * fix: repair Windows dev-channel updater * test: move copilot models-json injection coverage to plan tests * plugin-sdk: split command status surface * plugin-sdk: keep command status compatibility path light * plugin-sdk: drop investigative weixin repro harness * tests: document config mock choice for eager warmup * fix: update command-status SDK baseline (openclaw#63174) (thanks @hxy91819) * test: cap broad live model sweeps * fix: drop raw gateway chat control replies * test: make shared-token reload deterministic * test: isolate agentic suite smoke tests * test: replace models-config matrix with narrow coverage * test: isolate onboard skills status mock * plugins: add lightweight anthropic vertex discovery * test: isolate model auth module state * test: isolate subagent registry resume imports * plugins: keep google provider policy lightweight * test: keep ollama unreachable discovery on localhost * test: mock auth profile external overlay in oauth tests * auth: avoid plugin setup scans during common auth resolution * fix(logging): break console/logger type cycle * fix(config): stop owner-display barrel cycles * fix(commands): split auth choice apply types * fix(infra): extract exec approvals allowlist types * fix(commands): split doctor prompt option types * chore: prepare 2026.4.9-beta.1 release * chore: refresh config schema version for 2026.4.9-beta.1 * chore: refresh plugin SDK API baseline * test: run local full suite project shards in parallel * wizard: add explicit skip option to plugin setup (openclaw#63436) * Wizard: allow skipping plugin setup * Agents: reset nodes tool test modules * tests: reset discord native-command seams in model picker (openclaw#63267) * ci: tolerate noisy npm pack json output * test: isolate slack thread-ts recovery * fix(msteams): isolate channel thread sessions by replyToId (openclaw#58615) (openclaw#62713) * fix(msteams): isolate thread sessions by replyToId (openclaw#58615) * fix(msteams): align thread ID extraction + fix test types * fix(msteams): route thread replies to correct thread via replyToId (openclaw#58030) (openclaw#62715) * fix(msteams): pin reply target at inbound time to prevent DM/channel leak (openclaw#54520) (openclaw#62716) * test: keep local full suite serial by default * chore: prepare 2026.4.9 stable release * Agents: guard legacy pi transport override * Agents: restore upstream pi runner sources --------- Signed-off-by: sallyom <somalley@redhat.com> Co-authored-by: scoootscooob <zhentongfan@gmail.com> Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com> Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com> Co-authored-by: Tyler Warburton <Ethan.gold-Steinberg@protonmail.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us> Co-authored-by: Eric Curtin <eric.curtin@docker.com> Co-authored-by: Mariano <mbelinky@gmail.com> Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Co-authored-by: Frank Yang <frank.ekn@gmail.com> Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net> Co-authored-by: Pavan Kumar Gondhi <pgondhi@nvidia.com> Co-authored-by: Devin Robison <drobison@nvidia.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Co-authored-by: Agustin Rivera <31522568+eleqtrizit@users.noreply.github.com> Co-authored-by: zsx <git@zsxsoft.com> Co-authored-by: Devin Robison <drobison00@users.noreply.github.com> Co-authored-by: Eva H <63033505+hoyyeva@users.noreply.github.com> Co-authored-by: BruceMacD <5853428+BruceMacD@users.noreply.github.com> Co-authored-by: Shakker <shakkerdroid@gmail.com> Co-authored-by: lukeboyett <46942646+lukeboyett@users.noreply.github.com> Co-authored-by: Altay <altay@uinaf.dev> Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com> Co-authored-by: Accunza <12242811+duqaXxX@users.noreply.github.com> Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com> Co-authored-by: Pinghuachiu <9033138+Pinghuachiu@users.noreply.github.com> Co-authored-by: Radek Sienkiewicz <mail@velvetshark.com> Co-authored-by: Sally O'Malley <somalley@redhat.com> Co-authored-by: Vignesh Natarajan <vignesh.natarajan92@gmail.com> Co-authored-by: Mason Huang <masonxhuang@tencent.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org> Co-authored-by: pashpashpash <nik@vault77.ai> Co-authored-by: sudie-codes <suvenkat95@gmail.com>
…astRoute (openclaw#58013) Merged via squash. Prepared head SHA: 820ea20 Co-authored-by: duqaXxX <12242811+duqaXxX@users.noreply.github.com> Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com> Reviewed-by: @jalehman
…astRoute (openclaw#58013) Merged via squash. Prepared head SHA: 820ea20 Co-authored-by: duqaXxX <12242811+duqaXxX@users.noreply.github.com> Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com> Reviewed-by: @jalehman
…astRoute (openclaw#58013) Merged via squash. Prepared head SHA: 820ea20 Co-authored-by: duqaXxX <12242811+duqaXxX@users.noreply.github.com> Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com> Reviewed-by: @jalehman
Problem
Fixes #54441
Inter-session messages (
sessions_send) always arrive withchannel=webchat. WhenresolveLastChannelRaw/resolveLastToRawprocessed these turns they could overwrite a previously-established externallastChannel/lastTo(e.g.discord,telegram) with webchat routing identifiers, breaking delivery on all subsequent turns of that session.Root cause: both resolver functions had no visibility into whether the current turn originated from
sessions_send(inter-session) vs a real user message. The existing guard only protected against the webchat-with-established-external-route case for direct-session keys (fix #47745), but not for the general inter-session case.Fix
session-delivery.ts— addisInterSession?: booleanto bothresolveLastChannelRawandresolveLastToRaw. Whentrueand an external route is already persisted, return the persisted route unchanged. The fast-path sits above all other logic and adds zero overhead for non-inter-session turns.session.ts— importisInterSessionInputProvenancefromsrc/sessions/input-provenance.tsand passisInterSession: isInterSessionInputProvenance(ctx.InputProvenance)to both resolvers at the existing call site (~line 440).Behaviour for sessions with no external route established is fully preserved — inter-session can still set the initial route on brand-new sessions.
Files changed
src/auto-reply/reply/session-delivery.tsisInterSessionparam + guard to both resolverssrc/auto-reply/reply/session.tsisInterSessionInputProvenance, pass flag at call sitessrc/auto-reply/reply/session-delivery.test.tsTests
All 18 tests in
session-delivery.test.tspass (12 pre-existing + 6 new):lastChannellastChannellastTolastChannelnormallypersistedLastTo— no crash, falls through