Skip to content

fix(sessions): preserve Matrix room-id case in session keys (#75670)#87366

Merged
steipete merged 16 commits into
openclaw:mainfrom
lukeboyett:fix/matrix-roomid-case-session-key-75670
May 27, 2026
Merged

fix(sessions): preserve Matrix room-id case in session keys (#75670)#87366
steipete merged 16 commits into
openclaw:mainfrom
lukeboyett:fix/matrix-roomid-case-session-key-75670

Conversation

@lukeboyett

@lukeboyett lukeboyett commented May 27, 2026

Copy link
Copy Markdown
Contributor

Fixes #75670.

Summary

Matrix room IDs (and thread event IDs) are opaque and case-sensitive per the Matrix spec, but OpenClaw's session-key canonicalization lowercased them. That forked one room into duplicate sessions and produced 403 M_FORBIDDEN on recovery/identity paths that reconstruct the target from the (lowercased) session key — even though deliveryContext.to stayed correct. This is the session-key layer of #75670 (the issue's thread-event symptom was already handled by an injected identity thread-normalizer; the room-ID/session-key layer was not).

Instead of a Matrix-specific patch, this adds a generic, opt-in case-preservation registry that defaults to today's lowercase behavior and lets channels enroll individually. Signal group preservation (#82853) is encoded to match its prior behavior exactly; Matrix channel/group is the newly-enrolled channel. Every un-enrolled channel is byte-for-byte unchanged.

Regression history

Related / prior work

What changes

  • CASE_PRESERVING_PEERS registry + isCasePreservingPeer(channel, peerKind) (session-key-utils.ts) — single policy source. signal/group = segment span (unscoped, behavior-identical to the old regex); matrix/channel|group = tail span (opaque room id incl. embedded :server, plus any :thread:<event> suffix).
  • Consulted at the three sites that lowercased the room id:
    • construction — normalizeSessionPeerId
    • store canonicalization — normalizeSessionKeyPreservingOpaquePeerIds (rewritten to collect preserved spans and lowercase only the complement; tail matcher anchored to the agent-scoped head)
    • gateway send — explicit request.sessionKey now uses the opaque-preserving normalizer instead of raw lowercase
  • Exact mixed-case wins over folded legacy in resolveSessionStoreEntry and delivery-info.ts lookup (exact ranks below routability, above freshness). Legacy lowercased rows collapse toward the exact key on the next write.
  • Case-distinct safety (store-entry.ts): a folded lowercase key is treated as a collapsible/returnable legacy alias only when its delivery target confirms it is this room's lowercased artifact (not a genuinely case-distinct sibling room). Distinct rooms are never deleted or cross-read. (See "Codex review findings" below.)

Compatibility expectations

  • Signal group preservation unchanged (scoped + unscoped Signal:Group:<id>; thread suffix still lowercased).
  • Non-Matrix channels (Telegram, Slack, LINE, …) — keys lowercased exactly as before; a non-Matrix peer id literally containing :thread: is unaffected.
  • Matrix DM / MXID — out of scope by decision; stays lowercased.
  • Migration is fallback + write-time-collapse, not a bulk rewrite of stored session files.

Real behavior proof

  • Behavior or issue addressed: Matrix room IDs were lowercased into session keys (agent:<a>:matrix:channel:<lowercased-room-id>), forking sessions and causing 403 M_FORBIDDEN on send/recovery paths that rebuild the target from the key, even though the bot is joined to the real mixed-case room.
  • Real environment tested: A long-running production OpenClaw gateway (self-hosted Matrix homeserver, multi-account, macOS) running this exact patch on top of current main. The gateway was reloaded onto the patched build (PID confirmed on the new dist); two rooms that had logged the bug were exercised with live traffic. Room IDs / homeserver / MXID partially masked below; ellipses preserve the real opaque bytes' case.
  • Exact steps or command run after this patch: (1) pnpm build then reload the LaunchDaemon; (2) confirm the running process is on the patched dist; (3) let organic traffic hit room A and send a fresh human message into room B; (4) inspect the live session store (~/.openclaw/agents/<agent>/sessions/sessions.json) and the gateway delivery logs for both rooms; (5) run the existing matrix-403 watcher.
  • Evidence after fix (copied live output, partially masked):
    • Before (old build), the recurring failuregateway.err.log:
      2026-05-26T10:56:48 [tools] message failed: MatrixError: [403] User @m…ry:<server> not in room !trpa…zeflu:<server>
      2026-05-26T10:32:08 [tools] message failed: MatrixError: [403] User @m…ry:<server> not in room !stmc…cqvxy:<server>
      
      (note the lowercased room id in the failing send target).
    • After (patched build), room B (sage room) — same send now succeedsgateway.log:
      2026-05-27T13:14:58 setting pendingEvent status to sent in !trPa…zEflU:<server>
        event ID ~!trPa…zEflU:<server>:m1779902098492.0 -> $N91n…di8o
      
      delivered to the mixed-case room with a real Matrix event id; and the new persisted channel session key is mixed-case agent:<a>:matrix:channel:!trPa…zEflU:<server> (a single key, no fork).
    • Room A (dev room) — persisted session key for the live routable session is now mixed-case agent:<a>:matrix:channel:!StMc…CqvXy:<server> with deliveryContext.to = room:!StMc…CqvXy:<server>.
    • Zero case-403s on either room since the reload; the matrix-403 watcher reports 0 case-bug events in the post-reload window.
  • Observed result after fix: outbound sends to mixed-case Matrix rooms succeed where they previously 403'd; lookup/recovery/identity see the correct mixed-case key. Non-Matrix channels and Signal behave exactly as before.
  • What was not tested:
    • The documented main:thread:<event> residual (a thread key with no <channel>:<peerKind>: boundary still lowercases the event id — the main-session thread shape, not the room-session shape behind this issue).
    • Windows/Linux runtime (logic is platform-independent).
    • Separate surface (not this PR): some cron jobs carry pre-fix lowercased sessionTarget/sessionKey in their stored config. On fire they replay the old lowercased key (bypassing the now-fixed construction path) and materialize a non-routable, hollow session record (no deliveryContext, no origin) — which causes no 403 (the routable session is the mixed-case one). This is stale cron-config data, overlapping [Bug]: cron with sessionTarget=current still lowercases Matrix roomId in 2026.4.27 (regression of #71798 via MCP loopback path) #75261, and is corrected by a data migration of the cron configs, not by this PR's code.
    • pnpm check passes except a pre-existing, unrelated npm shrinkwrap guard failure (AWS-SDK lockfile/node_modules version skew) — this PR touches no package.json/lockfile.

Test plan

  • pnpm build — clean.
  • pnpm tsgo:core:test — clean.
  • pnpm vitest run src/sessions/session-key-case-preservation.test.ts src/config/sessions/delivery-info.test.ts src/gateway/session-utils.test.ts — 314 passed.
  • New src/sessions/session-key-case-preservation.test.ts regression matrix:
    • registry membership (Matrix channel/group + Signal group enrolled; DM / non-Matrix not);
    • construction + store-canon preserve Matrix room id (incl. :server) and :thread:<event> suffix;
    • case-distinct rooms never collapse to one key;
    • Signal scoped + unscoped preserved, Signal thread suffix lowercased, segment whitespace trimmed (legacy parity);
    • non-Matrix / :thread:-shaped peer id / Matrix DM all stay lowercased;
    • resolveSessionStoreEntry: case-distinct sibling neither deleted nor returned as existing; confirmed lowercased artifact still collapses + reads;
    • documented main:thread residual.
  • Local codex review --base origin/main: AGREE.

Acceptance criteria (from the #75670 handoff)

  • Matrix room IDs preserved exactly in new session keys.
  • Matrix thread event IDs preserved (room-scoped path).
  • Store lookup / delivery do not re-lowercase the :matrix:channel:<room-id> suffix.
  • Legacy lowercased entries treated as aliases (exact mixed-case wins) without deleting case-distinct rooms.
  • Regression tests cover mixed-case room, thread event, delivery metadata, no duplicate/cross-contaminated rows, and non-Matrix behavior unchanged.

Codex review findings (addressed)

  • P2 — "Do not delete case-distinct Matrix sessions" (codex-connector, inline on store-entry.ts): the original folded-legacy collapse could delete (and read-cross-contaminate) a genuinely case-distinct sibling room. Resolved: the folded lowercase key is materialized as a legacy alias for both delete and read only when isLowercasedLegacyAlias(...) confirms it delivers to this room's real mixed-case id; the loop now collapses only true canonical aliases. Three+ new tests cover it. Local codex re-review: AGREE, P2 fully resolved.

AI-assisted disclosure 🤖

AI-assisted (Claude). Developed and reviewed via an iterative local codex review loop (multiple plan rounds + diff reviews, ending AGREE, including the P2 follow-up above). The real-behavior proof is from a human-operated production gateway, not generated. Plan/review notes available on request.

Notes for review

  • Touches session-state + message-delivery (hence the merge-risk labels) — the case-distinct safety logic and the registry default-lowercase make the blast radius opt-in and non-Matrix-neutral.
  • The fix is currently deployed on the author's production gateway ahead of merge (local patch), which produced the proof above; it drops on rebase once this lands.
  • Allow edits by maintainers is enabled (takeover-ready).

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: M labels May 27, 2026
@clawsweeper

clawsweeper Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed May 27, 2026, 7:17 PM ET / 23:17 UTC.

Summary
The PR adds opt-in case preservation for Matrix channel/group session keys and Signal group keys, updates session-store, delivery lookup, gateway send, thread recovery, and state migration paths, and adds Matrix case-preservation regression coverage.

PR surface: Source +17444, Tests +681, Other +4. Total +18129 across 15 files.

Reproducibility: yes. source inspection shows current main lowercases Matrix peer IDs outside Signal group preservation, and the PR/linked issue provide concrete Matrix 403 and duplicate-session evidence. I did not run a live Matrix reproduction in this read-only review.

Review metrics: 1 noteworthy metric.

  • Generated browser asset rewrite: 1 asset rewritten, +17189/-52. The changed asset is executable plugin runtime code and is unrelated to the Matrix session-key behavior under review.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

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

Rank-up moves:

  • Replace substring Matrix alias proof with exact room/alias and thread equality plus regressions.
  • Remove the unrelated extensions/diffs/assets/viewer-runtime.js rewrite from this branch.
  • Rerun the focused session, delivery-info, migration, Matrix route, oxlint, typecheck, and whitespace checks after repair.

Risk before merge

  • Matrix session-key compatibility is intentionally changing: if alias proof is wrong, case-distinct rooms or threads can still be cross-read, collapsed, or recovered under the wrong target.
  • The branch rewrites an unrelated generated browser runtime under the diffs plugin; that executable asset is not explained by the Matrix session fix and needs removal or dedicated provenance.
  • The strongest live proof is from the contributor's production gateway before later maintainer fixups; the latest branch has focused local validation but no post-fix live Matrix homeserver run.

Maintainer options:

  1. Fix exact alias proof before merge (recommended)
    Replace substring-based Matrix delivery proof with exact extracted room or alias equality plus exact thread-id equality, add regressions for false-positive alias matches, and remove the unrelated generated diffs asset rewrite.
  2. Accept the compatibility risk explicitly
    Maintainers could intentionally merge after documenting why substring proof and the generated asset rewrite are acceptable, but that would leave session-state and generated-code review risk in the release.
  3. Pause for a narrower branch
    If the generated asset and session alias concerns are too intertwined to repair cleanly, pause this branch and ask for a narrow sessions-only PR that preserves the useful Matrix fix.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Replace Matrix folded-alias proof with exact extracted room/alias/thread equality, add focused regression tests that a substring-like delivery target is not accepted as a legacy alias, and revert extensions/diffs/assets/viewer-runtime.js unless a dedicated diffs source/proof change is added.

Next step before merge
A narrow automated repair can address both blockers by making Matrix alias proof exact and removing the unrelated generated runtime asset.

Security
Needs attention: No secret or dependency changes were found, but the unrelated generated browser runtime rewrite is a concrete supply-chain review concern.

Review findings

  • [P2] Compare Matrix delivery proof exactly — src/config/sessions/store-entry.ts:88-90
  • [P2] Remove the unrelated viewer runtime rewrite — extensions/diffs/assets/viewer-runtime.js:1
Review details

Best possible solution:

Keep the Matrix opaque-ID preservation design, replace folded Matrix alias checks with exact extracted room/alias and thread equality, remove the unrelated generated asset rewrite, and then land after focused session/delivery/migration validation.

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

Yes, source inspection shows current main lowercases Matrix peer IDs outside Signal group preservation, and the PR/linked issue provide concrete Matrix 403 and duplicate-session evidence. I did not run a live Matrix reproduction in this read-only review.

Is this the best way to solve the issue?

No, not as currently submitted. The case-preservation direction is the right fix boundary, but the branch still needs exact Matrix alias proof and removal of unrelated generated browser runtime churn.

Full review comments:

  • [P2] Compare Matrix delivery proof exactly — src/config/sessions/store-entry.ts:88-90
    The folded-alias proof still accepts a stored entry when normalizedBaseKey.includes(target). Matrix room and thread IDs are case-sensitive opaque targets, so substring proof can still classify the wrong stored entry as a legacy alias and let lookup or cleanup use the wrong room/thread. Extract the Matrix room or alias tail and compare it, plus the thread id when present, for equality.
    Confidence: 0.86
  • [P2] Remove the unrelated viewer runtime rewrite — extensions/diffs/assets/viewer-runtime.js:1
    This Matrix session fix rewrites extensions/diffs/assets/viewer-runtime.js from a compact generated bundle into a 17k-line browser runtime without a related diffs source change or proof. That executable generated asset is unrelated to the fix and should be reverted, or moved to a dedicated generated-asset PR with source/provenance.
    Confidence: 0.92

Overall correctness: patch is incorrect
Overall confidence: 0.88

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against c20a055341ac.

Label changes

Label justifications:

  • P1: The PR targets active Matrix channel workflows where lowercased room/thread IDs can break sends, recovery, and session state for real users.
  • merge-risk: 🚨 compatibility: The PR changes canonical session-key behavior and legacy alias handling for stored Matrix and Signal sessions during upgrade.
  • merge-risk: 🚨 message-delivery: Incorrect Matrix key or alias selection can route sends/recovery to a lowercased or wrong room/thread target.
  • merge-risk: 🚨 session-state: The store lookup and migration changes can collapse, preserve, or ignore persisted session records, so exact alias safety matters before merge.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Override: A maintainer applied proof: override for this PR.
Evidence reviewed

PR surface:

Source +17444, Tests +681, Other +4. Total +18129 across 15 files.

View PR surface stats
Area Files Added Removed Net
Source 7 17558 114 +17444
Tests 6 708 27 +681
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 2 14 10 +4
Total 15 18280 151 +18129

Security concerns:

  • [medium] Unrelated generated browser runtime rewrite — extensions/diffs/assets/viewer-runtime.js:1
    The PR changes a served JavaScript runtime asset by thousands of lines without a matching diffs source change or generated-asset provenance, so reviewers cannot attribute the executable code to the Matrix session fix.
    Confidence: 0.9

Acceptance criteria:

  • node scripts/run-vitest.mjs src/config/sessions/delivery-info.test.ts src/sessions/session-key-case-preservation.test.ts src/config/sessions/transcript.test.ts src/plugin-sdk/thread-aware-outbound-session-route.test.ts src/commands/doctor-state-migrations.test.ts extensions/matrix/src/session-route.test.ts
  • node scripts/run-oxlint.mjs src/config/sessions/delivery-info.test.ts src/config/sessions/delivery-info.ts src/config/sessions/store-entry.ts src/sessions/session-key-case-preservation.test.ts src/sessions/session-key-utils.ts extensions/matrix/src/session-route.test.ts src/commands/doctor-state-migrations.test.ts src/infra/state-migrations.ts
  • git diff --check

What I checked:

  • Repository policy applied: Root and scoped guides were read; session state, message delivery, plugin SDK, gateway, scripts, and generated asset changes are compatibility-sensitive under this repo's review policy. (AGENTS.md:1, c20a055341ac)
  • Current main lowercases Matrix peers: Current main preserves Signal group IDs but lowercases all other peer IDs, so Matrix room IDs are folded before this PR. (src/sessions/session-key-utils.ts:35, c20a055341ac)
  • PR implements a case-preserving registry: The merge result enrolls Matrix channel/group peers as tail-preserved opaque IDs and keeps Signal group behavior as segment-preserved. (src/sessions/session-key-utils.ts:46, be0cd4f3771c)
  • Explicit gateway session keys now use the opaque-preserving normalizer: Gateway send no longer raw-lowercases an explicit request session key; it calls the new preserving normalizer before resolving the session agent. (src/gateway/server-methods/send.ts:571, be0cd4f3771c)
  • Folded Matrix alias proof remains substring-based: The merge result still accepts delivery proof with normalizedBaseKey.includes(target) in both confirmed-alias and mismatch checks, leaving an inexact proof path in the session-store contract. (src/config/sessions/store-entry.ts:88, be0cd4f3771c)
  • Unrelated generated browser asset rewrite: The PR rewrites the diffs viewer runtime from a compact generated bundle to a 17k-line asset without a matching diffs source change or proof in the Matrix session fix. (extensions/diffs/assets/viewer-runtime.js:1, be0cd4f3771c)

Likely related people:

  • steipete: Current main blame for the session helper files points to recent refactor work, and the PR discussion shows maintainer fixups across session lookup, migration, and tests. (role: recent area contributor and PR fixup author; confidence: high; commits: 4da2b5f4d904, da59bf552c77, 55f4652c8a05; files: src/sessions/session-key-utils.ts, src/config/sessions/store-entry.ts, src/config/sessions/delivery-info.ts)
  • gumadeiras: Authored the merged Matrix ACP thread-binding fix that previously addressed another opaque Matrix target casing surface in the related cluster. (role: adjacent Matrix thread-routing owner; confidence: medium; commits: 5d2225212d0e; files: src/agents/acp-spawn.ts, extensions/matrix/src/session-route.test.ts)
  • lukeboyett: Authored the initial branch and supplied the live production Matrix logs that demonstrate the lowercased-room failure and the after-patch success path; not the sole current-main owner. (role: current PR branch author and live Matrix repro reporter; confidence: medium; commits: a5cbd256d869; files: src/sessions/session-key-utils.ts, src/config/sessions/store-entry.ts, src/config/sessions/delivery-info.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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

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

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

Copy link
Copy Markdown

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: c6d63dff87

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/config/sessions/store-entry.ts Outdated
@openclaw-barnacle openclaw-barnacle Bot added the triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. label May 27, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. labels May 27, 2026
@clawsweeper

clawsweeper Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🔥 Warming up: real-behavior proof passed; findings, security review, or rank-up moves are still in progress.

Hatch command

Comment @clawsweeper hatch when this PR is hatchable.

Hatchability rules:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.
What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchability usually comes from sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness. A merged PR is already final, so merge makes the egg hatchable independently.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

@openclaw-barnacle openclaw-barnacle Bot added triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. and removed triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. proof: sufficient ClawSweeper judged the real behavior proof convincing. labels May 27, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 27, 2026
@lukeboyett lukeboyett force-pushed the fix/matrix-roomid-case-session-key-75670 branch from c6d63df to 31be7a5 Compare May 27, 2026 19:23
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 27, 2026
@lukeboyett

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

The proof and codex finding are both addressed:

  • Real behavior proof rewritten with copied live before/after output from a production gateway (partially masked): the recurring [403] not in room <lowercased> send now delivers status to sent against the mixed-case room id, zero case-403s post-reload. Unit tests/repro are supplemental only.
  • codex-connector P2 ("do not delete case-distinct Matrix sessions") resolved — folded-legacy collapse + read fallback are now gated on a delivery-target confirmation; case-distinct rooms are never deleted or cross-read. New regression tests added; local codex review AGREE.
  • PR body restructured (regression history, related work, compatibility, test plan, acceptance criteria, codex findings, review notes).

No contributor-facing work open.

@clawsweeper

clawsweeper Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

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

Re-review progress:

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

acceptCandidate(normalizedIndex.get(foldedLegacyKey));

P2 Badge Do not reuse folded Matrix aliases in delivery lookup

When the requested Matrix session key is the lowercase room (or has no exact entry), this fallback index still looks up the lowercased/folded alias and can return a different mixed-case room entry that only folds to the same string. For example, with only agent:main:matrix:channel:!MixedCase:Example.Org in the store, extractDeliveryInfo("agent:main:matrix:channel:!mixedcase:example.org") can route to the mixed-case room because buildFreshestSessionEntryIndex indexes the mixed entry under the folded key; this reintroduces the same case-distinct room cross-contamination the new store-entry checks avoid.

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 27, 2026
@steipete steipete self-assigned this May 27, 2026
@lukeboyett lukeboyett force-pushed the fix/matrix-roomid-case-session-key-75670 branch from 31be7a5 to 522be22 Compare May 27, 2026 20:34
@openclaw-barnacle openclaw-barnacle Bot added size: L and removed size: M proof: sufficient ClawSweeper judged the real behavior proof convincing. labels May 27, 2026
@lukeboyett

Copy link
Copy Markdown
Contributor Author

@codex @clawsweeper — addressed the delivery-lookup case-distinct finding (force-pushed).

findSessionEntryInStore / buildFreshestSessionEntryIndex no longer let a case-distinct Matrix room leak into delivery resolution:

  • buildFreshestSessionEntryIndex no longer indexes entries under their folded lowercase key (only the exact opaque-preserving key).
  • Folded reads now require exact Matrix room-tail equality (matrixRoomIdOf extract + compare), not substring containment.
  • A new deliveryRoomCaseMismatch guard skips exact/trimmed/index candidates whose delivery room folds-equal but case-differs from the requested room (the mislabeled lowercased-artifact leak).
  • Non-Matrix keys (e.g. Signal groups) are unaffected — the guard is Matrix-room-scoped, and folded migration stays permissive (matrixRoomIdOf("") === "").

Migration read preserved (mixed request + lowercased artifact delivering to the same mixed room) and a Signal regression test guards the non-Matrix path. Local codex review: AGREE. No contributor-facing work open.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels May 27, 2026
@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations extensions: codex labels May 27, 2026
@steipete

Copy link
Copy Markdown
Contributor

Maintainer fixups pushed and re-reviewed.

Behavior addressed:

  • Preserves Matrix room IDs and Matrix thread event IDs in session keys instead of lowercasing opaque identifiers.
  • Prevents case-distinct Matrix rooms from collapsing into each other during store lookup, delivery lookup, send routing, current-thread recovery, and state migration/doctor canonicalization.
  • Keeps confirmed legacy folded Matrix artifacts recoverable when delivery metadata proves they belong to the mixed-case room/thread.
  • Keeps Signal and ordinary non-opaque alias freshness behavior intact, including fresher structural aliases for the same canonical Matrix key.

Real environment tested: local macOS checkout, Node/pnpm repo wrappers.

Exact steps or command run after this patch:

  • node scripts/run-vitest.mjs extensions/codex/src/app-server/attempt-startup.test.ts src/agents/embedded-agent-runner/model.test.ts src/plugin-sdk/thread-aware-outbound-session-route.test.ts src/config/sessions/delivery-info.test.ts src/sessions/session-key-case-preservation.test.ts src/config/sessions/transcript.test.ts extensions/matrix/src/session-route.test.ts extensions/slack/src/channel.test.ts src/commands/doctor-state-migrations.test.ts
  • node scripts/run-oxlint.mjs extensions/codex/src/app-server/attempt-startup.test.ts src/agents/embedded-agent-runner/model.provider-runtime.test-support.ts src/plugin-sdk/core.ts src/plugin-sdk/thread-aware-outbound-session-route.test.ts src/config/sessions/delivery-info.test.ts src/config/sessions/delivery-info.ts src/config/sessions/store-entry.ts src/sessions/session-key-case-preservation.test.ts src/sessions/session-key-utils.ts extensions/matrix/src/session-route.test.ts src/commands/doctor-state-migrations.test.ts src/infra/state-migrations.ts
  • git diff --check
  • pnpm tsgo:test
  • /Users/steipete/Projects/agent-scripts/skills/autoreview/scripts/autoreview --mode branch --base origin/main

Evidence after fix:

  • Vitest: 10 files passed, 366 tests passed.
  • Oxlint: passed.
  • Whitespace: passed.
  • Typecheck: passed.
  • Autoreview: clean, no accepted/actionable findings.

Observed result after fix: Matrix mixed-case room/thread keys stay distinct, legacy proven artifacts remain recoverable, and migration/doctor cleanup no longer re-lowercases valid Matrix keys.

What was not tested: live Matrix network delivery against a real homeserver.

# Conflicts:
#	extensions/codex/src/app-server/attempt-startup.test.ts
#	src/agents/embedded-agent-runner/model.provider-runtime.test-support.ts
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels May 27, 2026
@steipete

Copy link
Copy Markdown
Contributor

Post-merge update: rebased/merged the PR branch onto current origin/main, resolved the two current-main test-support conflicts, and added one more migration regression for unscoped legacy Matrix keys.

Behavior addressed: unscoped legacy matrix:channel:<room> session keys now preserve Matrix room casing when scoped into agent:<id>:... during doctor/startup migration.

Real environment tested: local macOS checkout, Node/pnpm repo wrappers.

Exact steps or command run after this patch:

  • node scripts/run-vitest.mjs extensions/codex/src/app-server/attempt-startup.test.ts src/agents/embedded-agent-runner/model.test.ts src/plugin-sdk/thread-aware-outbound-session-route.test.ts src/config/sessions/delivery-info.test.ts src/sessions/session-key-case-preservation.test.ts src/config/sessions/transcript.test.ts extensions/matrix/src/session-route.test.ts extensions/slack/src/channel.test.ts src/commands/doctor-state-migrations.test.ts
  • node scripts/run-oxlint.mjs extensions/codex/src/app-server/attempt-startup.test.ts src/agents/embedded-agent-runner/model.provider-runtime.test-support.ts src/plugin-sdk/core.ts src/plugin-sdk/thread-aware-outbound-session-route.test.ts src/config/sessions/delivery-info.test.ts src/config/sessions/delivery-info.ts src/config/sessions/store-entry.ts src/sessions/session-key-case-preservation.test.ts src/sessions/session-key-utils.ts extensions/matrix/src/session-route.test.ts src/commands/doctor-state-migrations.test.ts src/infra/state-migrations.ts
  • git diff --check
  • pnpm tsgo:test
  • /Users/steipete/Projects/agent-scripts/skills/autoreview/scripts/autoreview --mode branch --base origin/main

Evidence after fix:

  • Vitest: 10 files passed, 367 tests passed.
  • Oxlint: passed.
  • Whitespace: passed.
  • Typecheck: passed.
  • Autoreview: clean, no accepted/actionable findings.

What was not tested: live Matrix network delivery against a real homeserver.

@openclaw-barnacle openclaw-barnacle Bot added scripts Repository scripts extensions: diffs and removed agents Agent runtime and tooling extensions: codex labels May 27, 2026
@steipete steipete removed the status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. label May 27, 2026
@steipete

Copy link
Copy Markdown
Contributor

CI update before merge:

  • checks-node-agentic-agents failed once in unrelated src/agents/tools/image-tool.test.ts timing/state assertions, then passed on rerun in run 26544166839.
  • build-artifacts failed twice in gateway-watch-regression before the watch window because the CI runner source tree was already dirty. The job's tooling tests passed both times (184 passed, 2089 tests passed), and the failure was the runner guard: dirty_watched_tree.
  • Local targeted proof and final autoreview remain green as posted above.

@steipete steipete merged commit b5bd6e8 into openclaw:main May 27, 2026
174 of 179 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 28, 2026
…#75670) (openclaw#87366)

* fix(sessions): preserve Matrix room-id case in session keys (openclaw#75670)

Matrix room IDs (and thread event IDs) are opaque, case-sensitive per the
Matrix spec, but session-key canonicalization lowercased them. That forked
one room into duplicate sessions and produced 403 M_FORBIDDEN on recovery /
delivery paths that reconstruct the target from the (lowercased) session key,
even though deliveryContext.to stayed correct.

Introduce a generic, opt-in case-preservation registry (CASE_PRESERVING_PEERS)
consulted at all three lowercasing sites:
- construction: normalizeSessionPeerId
- store canonicalization: normalizeSessionKeyPreservingOpaquePeerIds
- gateway send: explicit request.sessionKey

Signal group preservation is encoded to match prior behavior exactly (segment
span, unscoped, thread suffix still lowercased). Matrix channel/group enrolls
the opaque tail (room id with embedded :server + any 🧵<event> suffix).
Exact mixed-case keys now win over folded legacy aliases in
resolveSessionStoreEntry and delivery-info lookup; existing lowercased rows
collapse on the next write. Matrix DM/MXID and non-enrolled channels keep the
default lowercase behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(sessions): guard Matrix folded alias delivery proof

* test(agents): cover cold OpenAI gpt-5.5 fallback

* fix(sessions): preserve non-opaque alias freshness

* fix(sessions): prevent Matrix cross-room thread recovery

* build(protocol): refresh tools effective Swift models

* test(codex): include effective cwd in startup fixture

* test(codex): align startup failure cleanup expectation

* fix(sessions): keep Signal folded aliases fresh

* fix(sessions): preserve unscoped Matrix room keys

* fix(sessions): recover legacy Matrix thread aliases

* fix(sessions): preserve Matrix keys in state migrations

* fix(sessions): keep Matrix structural alias freshness

* fix(sessions): preserve unscoped Matrix migration keys

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
eleboucher pushed a commit to eleboucher/homelab that referenced this pull request May 31, 2026
…026.5.28) (#759)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/openclaw/openclaw](https://openclaw.ai) ([source](https://github.com/openclaw/openclaw)) | patch | `2026.5.27` → `2026.5.28` |

---

### Release Notes

<details>
<summary>openclaw/openclaw (ghcr.io/openclaw/openclaw)</summary>

### [`v2026.5.28`](https://github.com/openclaw/openclaw/blob/HEAD/CHANGELOG.md#2026528)

[Compare Source](openclaw/openclaw@v2026.5.27...v2026.5.28)

##### Highlights

- Agent and Codex runtime recovery is steadier: subagents keep cwd/workspace separation, hook context stays prompt-local, session locks release on timeout abort while live OpenClaw locks survive cleanup, stale restart continuations are avoided, and Codex app-server/helper failures no longer tear down shared runtime state. ([#&#8203;87218](openclaw/openclaw#87218), [#&#8203;86875](openclaw/openclaw#86875), [#&#8203;87409](openclaw/openclaw#87409), [#&#8203;87399](openclaw/openclaw#87399), [#&#8203;87375](openclaw/openclaw#87375), [#&#8203;88129](openclaw/openclaw#88129))
- Channel delivery and session identity got safer across outbound plugin hooks, Matrix room ids, iMessage reactions/approvals, Slack final replies, Discord recovered tool warnings, runtime-config message actions, WhatsApp profile auth roots, Telegram polling, and Microsoft Teams service URL trust checks. ([#&#8203;73706](openclaw/openclaw#73706), [#&#8203;75670](openclaw/openclaw#75670), [#&#8203;87366](openclaw/openclaw#87366), [#&#8203;87451](openclaw/openclaw#87451), [#&#8203;87334](openclaw/openclaw#87334), [#&#8203;84535](openclaw/openclaw#84535), [#&#8203;82492](openclaw/openclaw#82492), [#&#8203;83304](openclaw/openclaw#83304), [#&#8203;87160](openclaw/openclaw#87160))
- Mobile and chat surfaces got a broader refresh: the iOS Pro UI, hosted push relay default, realtime Talk tab playback, Gateway chat transport, onboarding, Talk permissions, WebChat reconnect delivery, and session picker behavior now preserve more state across reconnects and empty searches. ([#&#8203;87367](openclaw/openclaw#87367), [#&#8203;87531](openclaw/openclaw#87531), [#&#8203;87682](openclaw/openclaw#87682), [#&#8203;88096](openclaw/openclaw#88096), [#&#8203;88105](openclaw/openclaw#88105)) Thanks [@&#8203;ngutman](https://github.com/ngutman) and [@&#8203;BunsDev](https://github.com/BunsDev).
- Browser, channel, and automation inputs are stricter: Browser tool timeouts, viewport/tab indices, Gateway ports, cron retry handling, Discord component ids, schema array refs, Telegram callback pages, and channel progress callbacks now reject malformed values earlier and preserve the intended delivery context. ([#&#8203;82887](openclaw/openclaw#82887))
- Provider, media, and document coverage expands with Claude Opus 4.8, Fal Krea image schemas, NVIDIA featured models, MiniMax streaming music responses, encrypted PDF extraction, voice model catalogs, GitHub Copilot agent runtime support, and a Codex Supervisor plugin path for delegated Codex workflows. ([#&#8203;87845](openclaw/openclaw#87845), [#&#8203;87890](openclaw/openclaw#87890), [#&#8203;80775](openclaw/openclaw#80775), [#&#8203;84764](openclaw/openclaw#84764), [#&#8203;87751](openclaw/openclaw#87751), [#&#8203;87794](openclaw/openclaw#87794))
- CLI, auth, doctor, and provider paths fail faster and recover more clearly: malformed numeric/version options are rejected, workspace dotenv provider credentials are ignored, heartbeat defaults, OAuth/token lifetimes, and local service startup requests are bounded, agent auth health labels are clearer, legacy `api_key` auth profiles migrate to canonical form, and restart guidance is actionable. ([#&#8203;87398](openclaw/openclaw#87398), [#&#8203;86281](openclaw/openclaw#86281), [#&#8203;87361](openclaw/openclaw#87361), [#&#8203;88133](openclaw/openclaw#88133), [#&#8203;83655](openclaw/openclaw#83655), [#&#8203;87559](openclaw/openclaw#87559), [#&#8203;88088](openclaw/openclaw#88088), [#&#8203;85924](openclaw/openclaw#85924)) Thanks [@&#8203;vincentkoc](https://github.com/vincentkoc) and [@&#8203;giodl73-repo](https://github.com/giodl73-repo).
- Plugin and Gateway hot paths do less repeated work while preserving cache correctness for install records, config JSON parsing, tool search catalogs, session stores, manifest model rows, auto-enabled plugin config, browser tokens, viewer assets, and release-split external plugin packages. ([#&#8203;86699](openclaw/openclaw#86699))
- Release, QA, and E2E validation now bound more log, artifact, harness, and cross-OS waits so failing lanes produce proof instead of hanging or false-greening.

##### Changes

- Status: show active subagent details in status output.
- Diffs: split the default language pack and expand default Diffs language coverage while keeping the host floor aligned. ([#&#8203;87370](openclaw/openclaw#87370), [#&#8203;87372](openclaw/openclaw#87372)) Thanks [@&#8203;RomneyDa](https://github.com/RomneyDa).
- ClawHub: add plugin display names plus skill verification and trust surfaces. ([#&#8203;87354](openclaw/openclaw#87354), [#&#8203;86699](openclaw/openclaw#86699)) Thanks [@&#8203;thewilloftheshadow](https://github.com/thewilloftheshadow) and [@&#8203;Patrick-Erichsen](https://github.com/Patrick-Erichsen).
- iOS: refresh the dev app with Pro Command, Chat, Agents, Settings, hosted push relay defaults, and realtime Talk playback wired to gateway sessions, diagnostics, chat, and realtime Talk. ([#&#8203;87367](openclaw/openclaw#87367), [#&#8203;88096](openclaw/openclaw#88096), [#&#8203;88105](openclaw/openclaw#88105)) Thanks [@&#8203;Solvely-Colin](https://github.com/Solvely-Colin) and [@&#8203;ngutman](https://github.com/ngutman).
- Docs: clarify Codex computer-use setup, paste-token stdin auth setup, macOS gateway sleep troubleshooting, native Codex hook relay recovery, container model auth, install deployment cards, device-token admin gating, CLI setup flow compatibility, Notte cloud browser CDP setup, and backport targets. ([#&#8203;87313](openclaw/openclaw#87313), [#&#8203;63050](openclaw/openclaw#63050), [#&#8203;87685](openclaw/openclaw#87685)) Thanks [@&#8203;bdjben](https://github.com/bdjben), [@&#8203;liaoandi](https://github.com/liaoandi), and [@&#8203;thewilloftheshadow](https://github.com/thewilloftheshadow).
- PDF/tools: use ClawPDF for PDF extraction, support encrypted PDF extraction, and surface MCP structured content in agent tool results. ([#&#8203;87670](openclaw/openclaw#87670), [#&#8203;87751](openclaw/openclaw#87751))
- Providers: add Claude Opus 4.8 support, Fal Krea image model schemas, NVIDIA featured model catalogs, MiniMax streaming music responses, and provider-backed voice model catalogs. ([#&#8203;87845](openclaw/openclaw#87845), [#&#8203;87890](openclaw/openclaw#87890), [#&#8203;80775](openclaw/openclaw#80775), [#&#8203;84764](openclaw/openclaw#84764), [#&#8203;87794](openclaw/openclaw#87794)) Thanks [@&#8203;eleqtrizit](https://github.com/eleqtrizit) and [@&#8203;vincentkoc](https://github.com/vincentkoc).
- Codex/GitHub: add the GitHub Copilot agent runtime and the Codex Supervisor plugin package.
- Plugins: externalize GitHub Copilot and Tokenjuice as official install-on-demand plugins with npm and ClawHub publish metadata.
- Workboard: add agent coordination tools for tracking and handing off active agent work.
- Discord: show commentary in progress drafts so live Discord runs expose useful in-progress context. ([#&#8203;85200](openclaw/openclaw#85200))
- Plugin SDK: add a reply payload sending hook for plugins that need to deliver channel-owned replies and flatten package types for SDK declarations. ([#&#8203;82823](openclaw/openclaw#82823), [#&#8203;87165](openclaw/openclaw#87165)) Thanks [@&#8203;piersonr](https://github.com/piersonr) and [@&#8203;RomneyDa](https://github.com/RomneyDa).
- Policy: add policy comparison, ingress-channel conformance, and sandbox-posture conformance checks. ([#&#8203;85572](openclaw/openclaw#85572), [#&#8203;85744](openclaw/openclaw#85744), [#&#8203;86768](openclaw/openclaw#86768))

##### Fixes

- Agents: fall back to local config pruning when the optional `agents delete` Gateway probe cannot authenticate, so offline installs can still delete agents without removing shared workspaces.
- Tighten phone-control mutation authorization \[AI]. ([#&#8203;87150](openclaw/openclaw#87150)) Thanks [@&#8203;pgondhi987](https://github.com/pgondhi987).
- Clarify directive persistence authorization policy \[AI]. ([#&#8203;86369](openclaw/openclaw#86369)) Thanks [@&#8203;pgondhi987](https://github.com/pgondhi987).
- Agents/Codex: keep spawned agent cwd/workspace state separated, forward ACP spawn attachments, keep hook context prompt-local, release session locks on timeout abort and runtime teardown without deleting live OpenClaw-owned locks during cleanup, avoid session event queue self-wait, clean up exec abort listeners, stream assistant deltas incrementally, recover raw missing-thread compaction failures, preserve rotated compaction session identity, keep compaction-timeout snapshots continuable, preserve shared app-server state across startup or helper failures, keep native hook relay alive across restarts and prune stale bridge files, close native hook relay replacement races, keep Claude live tool progress visible for watchdog recovery, suppress abandoned requester completion handoff, route workspace memory through tools, resolve Codex runtime models first, report quarantined dynamic tools, format `skills` command output, bind node auto-review to prepared plans, retry Claude CLI transcript probes, and bound compaction/steering retries. ([#&#8203;87218](openclaw/openclaw#87218), [#&#8203;86875](openclaw/openclaw#86875), [#&#8203;86123](openclaw/openclaw#86123), [#&#8203;88129](openclaw/openclaw#88129), [#&#8203;87399](openclaw/openclaw#87399), [#&#8203;87375](openclaw/openclaw#87375), [#&#8203;72574](openclaw/openclaw#72574), [#&#8203;87383](openclaw/openclaw#87383), [#&#8203;87400](openclaw/openclaw#87400), [#&#8203;83022](openclaw/openclaw#83022), [#&#8203;87671](openclaw/openclaw#87671), [#&#8203;87738](openclaw/openclaw#87738), [#&#8203;87747](openclaw/openclaw#87747), [#&#8203;87706](openclaw/openclaw#87706), [#&#8203;87546](openclaw/openclaw#87546), [#&#8203;87541](openclaw/openclaw#87541), [#&#8203;81048](openclaw/openclaw#81048)) Thanks [@&#8203;mbelinky](https://github.com/mbelinky), [@&#8203;Alix-007](https://github.com/Alix-007), [@&#8203;luoyanglang](https://github.com/luoyanglang), [@&#8203;yetval](https://github.com/yetval), [@&#8203;sjf](https://github.com/sjf), [@&#8203;joshavant](https://github.com/joshavant), [@&#8203;benjamin1492](https://github.com/benjamin1492), [@&#8203;c19354837](https://github.com/c19354837), [@&#8203;fuller-stack-dev](https://github.com/fuller-stack-dev), [@&#8203;pfrederiksen](https://github.com/pfrederiksen), and [@&#8203;dodge1218](https://github.com/dodge1218).
- Codex Supervisor: keep real-home app-server MCP session listing on the loaded state path, bound stored history scans, and close WebSocket probes cleanly.
- Channels: thread canonical session keys into outbound hooks, preserve Matrix room-id case, keep fallback tool warnings mention-inert, retain delivered Slack final replies during late cleanup, continue iMessage polling after denied reactions, suppress duplicate native exec approvals, resolve Gateway message actions against the active runtime config, preserve Telegram SecretRef prompt config and polling keepalives, preserve WhatsApp profile auth roots, QR display, document filenames, and plugin hook config, suppress Discord recovered tool warnings, preserve the Discord voice outbound helper, cap Discord/Signal/Zalo channel request and container timeouts, and block untrusted Teams service URLs while keeping TeamsSDK patterns aligned. ([#&#8203;73706](openclaw/openclaw#73706), [#&#8203;75670](openclaw/openclaw#75670), [#&#8203;87366](openclaw/openclaw#87366), [#&#8203;87451](openclaw/openclaw#87451), [#&#8203;87465](openclaw/openclaw#87465), [#&#8203;87334](openclaw/openclaw#87334), [#&#8203;84535](openclaw/openclaw#84535), [#&#8203;76262](openclaw/openclaw#76262), [#&#8203;83304](openclaw/openclaw#83304), [#&#8203;82492](openclaw/openclaw#82492), [#&#8203;87581](openclaw/openclaw#87581), [#&#8203;77114](openclaw/openclaw#77114), [#&#8203;86426](openclaw/openclaw#86426), [#&#8203;85529](openclaw/openclaw#85529), [#&#8203;87160](openclaw/openclaw#87160)) Thanks [@&#8203;zeroaltitude](https://github.com/zeroaltitude), [@&#8203;lukeboyett](https://github.com/lukeboyett), [@&#8203;jarvis-mns1](https://github.com/jarvis-mns1), [@&#8203;xiaotian](https://github.com/xiaotian), [@&#8203;funmerlin](https://github.com/funmerlin), [@&#8203;joshavant](https://github.com/joshavant), [@&#8203;eleqtrizit](https://github.com/eleqtrizit), [@&#8203;heyitsaamir](https://github.com/heyitsaamir), [@&#8203;amittell](https://github.com/amittell), [@&#8203;lidge-jun](https://github.com/lidge-jun), [@&#8203;liorb-mountapps](https://github.com/liorb-mountapps), [@&#8203;masatohoshino](https://github.com/masatohoshino), [@&#8203;bladin](https://github.com/bladin), and [@&#8203;giodl73-repo](https://github.com/giodl73-repo).
- CLI/auth/doctor/providers: reject malformed numeric/timeout/subcommand-version inputs, ignore workspace dotenv provider credentials, wait for respawn child shutdown, bound heartbeat defaults plus Codex, GitHub Copilot, OpenAI, Anthropic, Google, Feishu, LM Studio, MiniMax, Xiaomi TTS, and local-provider OAuth/token/model requests, harden Codex auth probes, label auth health by agent, preserve explicit agentRuntime pins during Codex model migration, warm provider auth off the main thread, honor Codex response timeouts, stop migrating current Claude Haiku 4.5 profiles to Sonnet, bound local service startup, resolve GPT-5.5 without cached catalog, migrate legacy memory auto-provider config, rewrite non-canonical `api_key` auth profiles, and make doctor restart follow-ups actionable. ([#&#8203;87398](openclaw/openclaw#87398), [#&#8203;86281](openclaw/openclaw#86281), [#&#8203;87361](openclaw/openclaw#87361), [#&#8203;88133](openclaw/openclaw#88133), [#&#8203;83655](openclaw/openclaw#83655), [#&#8203;87559](openclaw/openclaw#87559), [#&#8203;87719](openclaw/openclaw#87719), [#&#8203;88088](openclaw/openclaw#88088), [#&#8203;85924](openclaw/openclaw#85924), [#&#8203;84362](openclaw/openclaw#84362)) Thanks [@&#8203;Patrick-Erichsen](https://github.com/Patrick-Erichsen), [@&#8203;samzong](https://github.com/samzong), [@&#8203;giodl73-repo](https://github.com/giodl73-repo), [@&#8203;alkor2000](https://github.com/alkor2000), [@&#8203;mmaps](https://github.com/mmaps), [@&#8203;nxmxbbd](https://github.com/nxmxbbd), and [@&#8203;vincentkoc](https://github.com/vincentkoc).
- Gateway/security/session state: expire browser tokens after auth rotation, scope assistant idempotency dedupe, drain probe client closes, avoid stale restart continuation reuse, preserve retry-after fallbacks and stale rate-limit cooldown probes, bound webchat image and artifact transcript scans, include seconds in inbound metadata timestamps, clear completed session active runs, clear stale chat stream buffers, and evict current plugin-state namespaces at row caps. ([#&#8203;87810](openclaw/openclaw#87810), [#&#8203;87833](openclaw/openclaw#87833), [#&#8203;75089](openclaw/openclaw#75089)) Thanks [@&#8203;joshavant](https://github.com/joshavant) and [@&#8203;litang9](https://github.com/litang9).
- Config/parsing/network: reject partial numeric parsing, parse provider/Discord retry headers and dates strictly, honor IPv6 and bare IPv6 `no_proxy` entries, preserve empty plugin allowlists, canonicalize secret target array indexes, and reject malformed media content lengths, inspected TCP ports, marketplace content lengths, cron epochs, sandbox stat fields, unsafe duration values, empty config path segments, noncanonical schema array refs, unsafe Telegram callback pages, and invalid Teams attachment-fetch DNS targets. ([#&#8203;87883](openclaw/openclaw#87883)) Thanks [@&#8203;zhangguiping-xydt](https://github.com/zhangguiping-xydt).
- Browser/input hardening: reject invalid tab indexes, excessive viewport resizes, explicit zero CDP ports, malformed geolocation options, unsafe screenshot or permission-grant timeouts, loose response-body limits, invalid cookie expiries, and non-finite Browser tool delays/timeouts.
- Cron/automation: retry recurring jobs after transient model rate limits before waiting for the next scheduled slot, and preflight model fallbacks before skipping scheduled work. ([#&#8203;82887](openclaw/openclaw#82887)) Thanks [@&#8203;chen-zhang-cs-code](https://github.com/chen-zhang-cs-code).
- Auto-reply/directives: respect provider and relayed channel metadata during directive persistence so channel-originated decisions keep their intended context. ([#&#8203;87683](openclaw/openclaw#87683))
- WhatsApp: resolve the auth directory from the active profile so profile-scoped WhatsApp installs do not drift to the wrong credential root. ([#&#8203;82492](openclaw/openclaw#82492)) Thanks [@&#8203;lidge-jun](https://github.com/lidge-jun).
- Gateway/session state: clear completed session active runs, avoid cold-loading providers for MCP inventory, cache single-session child indexes, cap handshake timers, and bound preauth, auth-guard, media, transcript, readiness, and port options.
- Channels/replies: preserve channel-owned progress callbacks when verbose output is off, keep group-room progress suppression intact, prefer external session delivery context, escape Discord component id delimiters, force final TUI chat repaints, show Slack reasoning previews, and normalize Discord/Matrix/Mattermost channel numeric options. ([#&#8203;87476](openclaw/openclaw#87476), [#&#8203;87423](openclaw/openclaw#87423))
- Agents/tool args: harden smart-quoted argument repair for edit arrays and exact escaped arguments so model-produced tool calls recover without corrupting valid input. ([#&#8203;86611](openclaw/openclaw#86611)) Thanks [@&#8203;ferminquant](https://github.com/ferminquant).
- Providers/agents: preserve seeded Anthropic signatures, preserve signed thinking payloads, concatenate signature-delta chunks, preserve DeepSeek `reasoning_content` replay across tier suffixes, apply OpenRouter strict9 ids to Mistral routes, promote Ollama plain-text tool calls, load NVIDIA featured model catalogs, stream MiniMax music generation responses, and recover empty preflight compaction. ([#&#8203;87593](openclaw/openclaw#87593), [#&#8203;87493](openclaw/openclaw#87493), [#&#8203;80775](openclaw/openclaw#80775), [#&#8203;84764](openclaw/openclaw#84764)) Thanks [@&#8203;Pluviobyte](https://github.com/Pluviobyte) and [@&#8203;eleqtrizit](https://github.com/eleqtrizit).
- Media/images: skip CLI image cache refs when resolving generated images, allow trusted generated HTML attachments, and bound generated video downloads so stale refs and slow providers fail cleanly. ([#&#8203;87523](openclaw/openclaw#87523), [#&#8203;87982](openclaw/openclaw#87982))
- File transfer: handle late tar stdin pipe errors after archive validation or unpacking has already settled.
- Performance: trust install-record caches between reloads, prefer native JSON parsing, reuse unchanged tool-search catalogs, reuse gateway session and plugin metadata paths, skip unchanged store serialization, patch single-entry session writes, add precomputed session patch writers, reduce store clone allocations, cache manifest model catalog rows and auto-enabled plugin config, avoid full session snapshots for entry reads, defer configured Slack full startup, prefer bundled plugin dist entries, and slim current metadata identity caches. ([#&#8203;87760](openclaw/openclaw#87760))
- Docker/release/QA: package runtime workspace templates, stream cross-OS served artifacts, preserve sparse Crabbox run artifacts, isolate npm plugin installs per package, reject incompatible package plugin API installs, drop the leftover root Sharp dependency from package manifests after the Rastermill migration, bound OpenClaw instance logs, plugin gauntlet relay logs, MCP channel buffers, kitchen-sink scans, agent-turn assertions, QA-Lab credential broker calls, QA Matrix substrate requests, and release scenario logs, and keep release/google live guards current. ([#&#8203;87647](openclaw/openclaw#87647), [#&#8203;87477](openclaw/openclaw#87477)) Thanks [@&#8203;rohitjavvadi](https://github.com/rohitjavvadi) and [@&#8203;vincentkoc](https://github.com/vincentkoc).
- Release/CI: bound manual git fetches, ClawHub verifier responses, ClawHub owner metadata, dependency-guard error bodies, Parallels limits, startup/test/memory budget parsing, and diffs viewer build warnings so release lanes fail with useful proof instead of hanging. ([#&#8203;87839](openclaw/openclaw#87839))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19-->

Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/759
SYU8384 pushed a commit to SYU8384/openclaw that referenced this pull request Jun 3, 2026
…#75670) (openclaw#87366)

* fix(sessions): preserve Matrix room-id case in session keys (openclaw#75670)

Matrix room IDs (and thread event IDs) are opaque, case-sensitive per the
Matrix spec, but session-key canonicalization lowercased them. That forked
one room into duplicate sessions and produced 403 M_FORBIDDEN on recovery /
delivery paths that reconstruct the target from the (lowercased) session key,
even though deliveryContext.to stayed correct.

Introduce a generic, opt-in case-preservation registry (CASE_PRESERVING_PEERS)
consulted at all three lowercasing sites:
- construction: normalizeSessionPeerId
- store canonicalization: normalizeSessionKeyPreservingOpaquePeerIds
- gateway send: explicit request.sessionKey

Signal group preservation is encoded to match prior behavior exactly (segment
span, unscoped, thread suffix still lowercased). Matrix channel/group enrolls
the opaque tail (room id with embedded :server + any 🧵<event> suffix).
Exact mixed-case keys now win over folded legacy aliases in
resolveSessionStoreEntry and delivery-info lookup; existing lowercased rows
collapse on the next write. Matrix DM/MXID and non-enrolled channels keep the
default lowercase behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(sessions): guard Matrix folded alias delivery proof

* test(agents): cover cold OpenAI gpt-5.5 fallback

* fix(sessions): preserve non-opaque alias freshness

* fix(sessions): prevent Matrix cross-room thread recovery

* build(protocol): refresh tools effective Swift models

* test(codex): include effective cwd in startup fixture

* test(codex): align startup failure cleanup expectation

* fix(sessions): keep Signal folded aliases fresh

* fix(sessions): preserve unscoped Matrix room keys

* fix(sessions): recover legacy Matrix thread aliases

* fix(sessions): preserve Matrix keys in state migrations

* fix(sessions): keep Matrix structural alias freshness

* fix(sessions): preserve unscoped Matrix migration keys

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…#75670) (openclaw#87366)

* fix(sessions): preserve Matrix room-id case in session keys (openclaw#75670)

Matrix room IDs (and thread event IDs) are opaque, case-sensitive per the
Matrix spec, but session-key canonicalization lowercased them. That forked
one room into duplicate sessions and produced 403 M_FORBIDDEN on recovery /
delivery paths that reconstruct the target from the (lowercased) session key,
even though deliveryContext.to stayed correct.

Introduce a generic, opt-in case-preservation registry (CASE_PRESERVING_PEERS)
consulted at all three lowercasing sites:
- construction: normalizeSessionPeerId
- store canonicalization: normalizeSessionKeyPreservingOpaquePeerIds
- gateway send: explicit request.sessionKey

Signal group preservation is encoded to match prior behavior exactly (segment
span, unscoped, thread suffix still lowercased). Matrix channel/group enrolls
the opaque tail (room id with embedded :server + any 🧵<event> suffix).
Exact mixed-case keys now win over folded legacy aliases in
resolveSessionStoreEntry and delivery-info lookup; existing lowercased rows
collapse on the next write. Matrix DM/MXID and non-enrolled channels keep the
default lowercase behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(sessions): guard Matrix folded alias delivery proof

* test(agents): cover cold OpenAI gpt-5.5 fallback

* fix(sessions): preserve non-opaque alias freshness

* fix(sessions): prevent Matrix cross-room thread recovery

* build(protocol): refresh tools effective Swift models

* test(codex): include effective cwd in startup fixture

* test(codex): align startup failure cleanup expectation

* fix(sessions): keep Signal folded aliases fresh

* fix(sessions): preserve unscoped Matrix room keys

* fix(sessions): recover legacy Matrix thread aliases

* fix(sessions): preserve Matrix keys in state migrations

* fix(sessions): keep Matrix structural alias freshness

* fix(sessions): preserve unscoped Matrix migration keys

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: matrix Channel integration: matrix commands Command implementations extensions: diffs gateway Gateway runtime merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P1 High-priority user-facing bug, regression, or broken workflow. proof: override Maintainer override for the external PR real behavior proof gate. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. scripts Repository scripts size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Matrix thread session key case-normalizes event IDs, causing duplicate stuck sessions and thread delivery failures

2 participants