mattermost: fix DM media upload for unprefixed user IDs#29925
mattermost: fix DM media upload for unprefixed user IDs#29925mukhtharcm merged 4 commits intoopenclaw:mainfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9be1e8bad8
ℹ️ 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".
| } catch { | ||
| userIdResolutionCache.set(key, false); | ||
| target = { kind: "channel", id: trimmedTo }; |
There was a problem hiding this comment.
Cache channel fallback only for user-not-found responses
The catch branch treats every fetchMattermostUser failure as “not a user” and permanently caches false, so a transient API/network failure (or a 5xx/429) during one send will poison that recipient for the rest of the process: later sends skip user resolution, are forced down the channel path, and can no longer DM that user by bare ID. This fallback should be limited to confirmed not-found cases and non-404 errors should be surfaced/retried instead of cached as channel.
Useful? React with 👍 / 👎.
Greptile SummaryFixes Key changes:
Confidence Score: 4/5
Last reviewed commit: f2d6a9c |
|
Thanks for the reviews — I pushed an update that addresses both the CI failure and the caching concern raised by Codex:
@greptile review |
|
Update: the remaining CI failure was unrelated to the Mattermost change set.
I merged latest Local |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fc984eb0b5
ℹ️ 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 (/^[a-z0-9]{8,}$/i.test(trimmed)) { | ||
| return trimmed; |
There was a problem hiding this comment.
Canonicalize opaque IDs before context comparisons
Returning bare opaque IDs here changes the canonical form emitted by normalizeTargetForProvider, so 64if... and channel:64if... are no longer treated as the same destination. When currentChannelId was captured as a raw ID (for example from --to) and a later send uses an explicit prefix, isCrossContextTarget in src/infra/outbound/outbound-policy.ts will misclassify it as cross-context, which can add incorrect cross-context markers or deny the send when allowWithinProvider is disabled. Normalize both forms to a single canonical representation before policy checks.
Useful? React with 👍 / 👎.
|
Addressed both Codex review comments in commit P1 (cache poisoning on non-404 errors) — already handled in prior commit. The catch branch only caches P2 (canonicalize opaque IDs) — Added a comment in Merge conflict resolved — |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 12e158bfc9
ℹ️ 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".
| // user-first DM resolution (send.ts) requires the raw form to detect ambiguity. | ||
| // If this causes false cross-context mismatches in policy checks, callers should | ||
| // strip the "channel:" prefix before comparing against bare opaque IDs. | ||
| if (/^[a-z0-9]{8,}$/i.test(trimmed)) { |
There was a problem hiding this comment.
Keep opaque Mattermost IDs in canonical target form
Returning bare opaque IDs here breaks equality checks that rely on provider normalization for same-target detection. In Mattermost inbound handling, OriginatingTo is always prefixed (for channels: channel:<id>), but extractMessagingToolSend stores bare IDs after normalization and shouldSuppressMessagingToolReplies compares normalized strings with strict equality (targetKey === originTarget in src/auto-reply/reply/reply-payloads.ts). With this change, a send to a bare channel ID is no longer recognized as the same destination as the originating channel, so reply suppression can fail and produce duplicate follow-up replies in the same conversation.
Useful? React with 👍 / 👎.
12e158b to
669dd6c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 669dd6c229
ℹ️ 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".
CI Failure AnalysisThe 1.
|
669dd6c to
24c7b84
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 24c7b84f8f
ℹ️ 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".
.github/workflows/preflight.yml
Outdated
| on: | ||
| push: | ||
| branches-ignore: | ||
| - main |
There was a problem hiding this comment.
Remove fork-only preflight workflow from upstream
This workflow is configured to run on every non-main branch push (on.push.branches-ignore: [main]), so merging it into openclaw/openclaw will add an extra full pnpm check + pnpm build CI run for all branch pushes in the upstream repo. The file itself states it is fork-only and should not be submitted upstream, so keeping it in this commit introduces avoidable CI noise/cost and contradicts its documented scope.
Useful? React with 👍 / 👎.
24c7b84 to
46b9c61
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 46b9c61712
ℹ️ 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".
|
|
||
| describe("sendMessageMattermost user-first resolution", () => { | ||
| beforeEach(() => { | ||
| vi.clearAllMocks(); |
There was a problem hiding this comment.
Reset send-module caches between user-first tests
This beforeEach only clears mock call state, but send.ts stores userIdResolutionCache and dmChannelCache as module-level Maps that persist across tests; after resolves unprefixed 26-char id as user... runs, FAKE_USER_ID is cached as a user, so the later transient-error case can skip the intended fallback path and fail/order-depend on prior tests instead of validating the cache behavior it claims to test. Reset the module state (or reload the module) for each test in this block.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8b7c55812d
ℹ️ 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".
| resolvedKind === "user" || | ||
| (resolvedKind !== "channel" && | ||
| resolvedKind !== "group" && | ||
| (lower.startsWith("user:") || trimmed.startsWith("@"))); |
There was a problem hiding this comment.
Preserve user-prefixed Mattermost targets in session routing
This condition lets resolvedTarget.kind override an explicit user: target, so a send can be routed as a channel even when the normalized destination is a DM. A concrete path is mattermost:<id>: normalizeMattermostMessagingTarget rewrites it to user:<id>, but resolveMessagingTarget can still carry kind: "group"; with this new guard, resolveMattermostSession ignores the user: prefix and builds channel session metadata, which can break DM session continuity and cross-context classification for that send.
Useful? React with 👍 / 👎.
… uploads Fixes 403 Forbidden errors when sending media to Mattermost DMs configured with a bare user ID (e.g. in heartbeat.to or cron targets). A 26-character alphanumeric Mattermost ID is ambiguous: it could be either a user ID or a channel ID. Without user-first resolution, bare user IDs are passed directly to the file upload API as a channel ID, which returns 403. Changes: - Probe GET /api/v4/users/<id> before treating a bare 26-char ID as a channel. On success, create/reuse a DM channel via POST /channels/direct. - Cache positive user lookups and DM channel IDs to avoid repeated API calls. - Cache negative (404) lookups to skip the probe on known channel IDs. - Transient errors (429, 5xx, network) do NOT poison the cache — the probe is retried on the next send. - Explicit prefixes (user:, channel:, mattermost:, @) bypass the probe. - DM channel IDs returned by POST /channels/direct are cached per user ID. - Use strict isMattermostId() (26-char) for the ambiguity guard. Tests: added user-first resolution suite to send.test.ts covering the DM path, channel fallback, transient error no-cache behavior, and explicit prefix bypass.
…ix guidance - mattermost.md: replace 'Bare IDs are treated as channels' with accurate description of user-first resolution behavior. - cron-jobs.md: add note that bare 26-char Mattermost IDs use user-first resolution; recommend explicit prefixes for deterministic routing.
8b7c558 to
5cffcb0
Compare
|
Merged via squash.
Thanks @teconomix! |
* main: (43 commits) docs: add openclaw#42173 to CHANGELOG — strip leaked model control tokens (openclaw#42216) Agents: align onPayload callback and OAuth imports docs: add Tengji (George) Zhang to maintainer table (openclaw#42190) fix: strip leaked model control tokens from user-facing text (openclaw#42173) Changelog: add unreleased March 9 entries chore: add .dev-state to .gitignore (openclaw#41848) fix(agents): avoid duplicate same-provider cooldown probes in fallback runs (openclaw#41711) fix(mattermost): preserve markdown formatting and native tables (openclaw#18655) feat(acp): add resumeSessionId to sessions_spawn for ACP session resume (openclaw#41847) ACPX: bump bundled acpx to 0.1.16 (openclaw#41975) mattermost: fix DM media upload for unprefixed user IDs (openclaw#29925) fix(msteams): use General channel conversation ID as team key for Bot Framework compatibility (openclaw#41838) fix(mattermost): read replyTo param in plugin handleAction send (openclaw#41176) fix(sandbox): pass real workspace to sessions_spawn when workspaceAccess is ro (openclaw#40757) fix(ui): replace Manual RPC text input with sorted method dropdown (openclaw#14967) CI: select Swift 6.2 toolchain for CodeQL (openclaw#41787) fix(agents): forward memory flush write path (openclaw#41761) fix(telegram): move network fallback to resolver-scoped dispatchers (openclaw#40740) fix(security): harden replaceMarkers() to catch space/underscore boundary marker variants (openclaw#35983) fix(web-search): recover OpenRouter Perplexity citations from message annotations (openclaw#40881) ...
… creation Adds exponential backoff retry mechanism for Mattermost DM channel creation to handle transient failures (429, 5xx, network errors). Changes: - Add createMattermostDirectChannelWithRetry() with configurable retry options - Support maxRetries, initialDelayMs, maxDelayMs, timeoutMs parameters - Add dmChannelRetry config option to Mattermost account schema - Allow per-send override via dmRetryOptions in MattermostSendOpts - Add comprehensive tests for retry logic and error handling - Retry on: 429 rate limits, 5xx server errors, network/transient errors - Don't retry on: 4xx client errors (except 429) Fixes the gap identified in PR openclaw#29925 where initial DM channel creation failures had no retry mechanism.
Merged via squash. Prepared head SHA: 5cffcb0 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
Merged via squash. Prepared head SHA: 5cffcb0 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
Merged via squash. Prepared head SHA: 5cffcb0 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
Merged via squash. Prepared head SHA: 5cffcb0 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
Merged via squash. Prepared head SHA: 5cffcb0 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
Merged via squash. Prepared head SHA: 5cffcb0 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
Merged via squash. Prepared head SHA: 5cffcb0 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
Merged via squash. Prepared head SHA: 5cffcb0 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
Merged via squash. Prepared head SHA: 5cffcb0 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
Merged via squash. Prepared head SHA: 5cffcb0 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
… creation Adds exponential backoff retry mechanism for Mattermost DM channel creation to handle transient failures (429, 5xx, network errors). Changes: - Add createMattermostDirectChannelWithRetry() with configurable retry options - Support maxRetries, initialDelayMs, maxDelayMs, timeoutMs parameters - Add dmChannelRetry config option to Mattermost account schema - Allow per-send override via dmRetryOptions in MattermostSendOpts - Add comprehensive tests for retry logic and error handling - Retry on: 429 rate limits, 5xx server errors, network/transient errors - Don't retry on: 4xx client errors (except 429) Fixes the gap identified in PR openclaw#29925 where initial DM channel creation failures had no retry mechanism.
Merged via squash. Prepared head SHA: 5cffcb0 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
Merged via squash. Prepared head SHA: 5cffcb0 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
… creation Adds exponential backoff retry mechanism for Mattermost DM channel creation to handle transient failures (429, 5xx, network errors). Changes: - Add createMattermostDirectChannelWithRetry() with configurable retry options - Support maxRetries, initialDelayMs, maxDelayMs, timeoutMs parameters - Add dmChannelRetry config option to Mattermost account schema - Allow per-send override via dmRetryOptions in MattermostSendOpts - Add comprehensive tests for retry logic and error handling - Retry on: 429 rate limits, 5xx server errors, network/transient errors - Don't retry on: 4xx client errors (except 429) Fixes the gap identified in PR openclaw#29925 where initial DM channel creation failures had no retry mechanism.
… creation Adds exponential backoff retry mechanism for Mattermost DM channel creation to handle transient failures (429, 5xx, network errors). Changes: - Add createMattermostDirectChannelWithRetry() with configurable retry options - Support maxRetries, initialDelayMs, maxDelayMs, timeoutMs parameters - Add dmChannelRetry config option to Mattermost account schema - Allow per-send override via dmRetryOptions in MattermostSendOpts - Add comprehensive tests for retry logic and error handling - Retry on: 429 rate limits, 5xx server errors, network/transient errors - Don't retry on: 4xx client errors (except 429) Fixes the gap identified in PR openclaw#29925 where initial DM channel creation failures had no retry mechanism.
Problem
Sending media via the
messagetool to a Mattermost DM could fail with403 Forbidden.Root cause: in Mattermost, user IDs and channel IDs share the same opaque format. The outbound target normalizer treated unprefixed IDs as
channel:<id>, causing/api/v4/filesuploads to use a user id aschannel_id.Fix
sendMessageMattermost:GET /api/v4/users/<id>succeeds, resolve the DM channel viaPOST /api/v4/channels/directand send there.Docs
Document the new behavior in
docs/channels/mattermost.mdand update the cron target reminder.Tests
Added unit tests for normalization + recipient resolution.
Closes #29881.