Skip to content

fix(msteams): resolve Graph API chat ID for DM file uploads#49585

Merged
BradGroux merged 1 commit intoopenclaw:mainfrom
sudie-codes:fix/msteams-dm-file-chatid
Mar 20, 2026
Merged

fix(msteams): resolve Graph API chat ID for DM file uploads#49585
BradGroux merged 1 commit intoopenclaw:mainfrom
sudie-codes:fix/msteams-dm-file-chatid

Conversation

@sudie-codes
Copy link
Copy Markdown
Contributor

@sudie-codes sudie-codes commented Mar 18, 2026

Summary

Fixes #35822 — File attachments in 1:1 DM conversations fail because the Graph API rejects Bot Framework conversation IDs as invalid chat IDs.

What & Why

Problem: All file uploads in DM conversations fail with a 400 error from the Graph API. File sharing only works in group chats and channels. The Graph API's /chats/{chatId} endpoint rejects the Bot Framework conversation.id format.

Root cause: Bot Framework uses conversation IDs in formats like `a]...:b]...@unq.gbl.spaces` or `8:orgid:...`, while Graph API expects Graph-native chat IDs (format: `19:...@unq.gbl.spaces`). These are fundamentally different identifiers — simple suffix stripping via `normalizeConversationId()` at `messenger.ts:97-99` is insufficient. In `send.ts:209`, the raw Bot Framework `conversationId` was passed directly as `chatId` to Graph upload functions.

Fix:

  • graph-upload.ts: Added resolveGraphChatId() that queries GET /me/chats filtered by user AAD object ID to find the Graph-native 19:xxx chat ID
  • conversation-store.ts: Added optional graphChatId field to StoredConversationReference
  • send-context.ts: resolveMSTeamsSendContext now resolves and caches graphChatId; persists to conversation store for future sends (avoids repeated Graph API lookups)
  • send.ts:209: Uses ctx.graphChatId ?? conversationId instead of raw Bot Framework ID
  • messenger.ts:324: Uses conversationRef.graphChatId ?? conversationRef.conversation?.id
  • Graceful fallback: if Graph lookup fails, falls back to conversationId (group chats already use 19: format so they continue working)

Files changed: extensions/msteams/src/graph-upload.ts, extensions/msteams/src/conversation-store.ts, extensions/msteams/src/send-context.ts, extensions/msteams/src/send.ts, extensions/msteams/src/messenger.ts

Screenshots

N/A — This is a server-side Graph API integration fix. The change affects how chat IDs are resolved when uploading files to OneDrive/SharePoint via Graph API. Verification requires a running bot with Graph API permissions (Chat.Read) and a Teams 1:1 DM conversation. The fix is validated via unit tests that mock the Graph API response.

Test Results

  • 5 new tests in graph-upload.test.ts for resolveGraphChatId (success, no match, error fallback, caching)
  • 2 new tests in send.test.ts verifying chatId propagation to SharePoint upload
  • All 237 msteams tests pass (27 test files)

AI Disclosure

  • AI-assisted (Claude Code with team orchestration)
  • Fully tested — new tests cover resolution, fallback, and caching paths
  • I understand what the code does: resolves Graph-native chat ID via `GET /me/chats` API, caches the mapping in the conversation store, and uses the resolved ID for all Graph API file upload operations

@openclaw-barnacle openclaw-barnacle Bot added channel: msteams Channel integration: msteams size: M labels Mar 18, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 18, 2026

Greptile Summary

This PR fixes DM file attachment failures in MS Teams by resolving the mismatch between Bot Framework personal-chat conversation IDs (a:1xxx / 8:orgid:xxx) and the Graph API's required 19:xxx@thread.tacv2 format. The core fix — resolveGraphChatId in graph-upload.ts plus plumbing through send-context.ts, send.ts, and messenger.ts — is correct and well-tested. Two additional changes are bundled: a singleton guard in monitor.ts to prevent EADDRINUSE crash-loops on double-start, and a security fix in message-handler.ts (GHSA-g7cr-9h7q-4qxq) that closes an allowlist bypass when the sender allowlist is empty.

Issues found:

  • Wrong-chat risk in resolveGraphChatId fallback (graph-upload.ts lines 308–334): When userAadObjectId is absent the function queries all 1:1 chats and returns the first result arbitrarily. The comment says "rely on threadId matching below" but no such matching exists. If the bot has multiple 1:1 conversations, this could resolve to the wrong user's chat and share files there. The fix is to return null when the list is ambiguous and userAadObjectId is unavailable, rather than guessing.

  • Failed Graph resolution not cached (send-context.ts lines 177–181): When resolveGraphChatId returns null, the result is never written to the conversation store. ref.graphChatId remains undefined, so every subsequent send for that conversation will trigger another Graph API round-trip even when resolution consistently fails. Caching a negative result (or a sentinel) would bound the retry behaviour.

Confidence Score: 3/5

  • Safe to merge for most deployments, but the ambiguous fallback path in resolveGraphChatId could share files to the wrong 1:1 chat when userAadObjectId is not stored.
  • The primary fix is correct and well-tested. However, the no-userAadObjectId fallback in resolveGraphChatId has a real wrong-chat risk (incomplete "threadId matching" that was never implemented), which is a potential privacy/correctness issue in production deployments where bots serve many users. The null-cache omission is a lesser performance concern. These lower confidence from a clean merge.
  • extensions/msteams/src/graph-upload.ts (fallback resolution logic), extensions/msteams/src/send-context.ts (null result caching)
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/msteams/src/graph-upload.ts
Line: 308-334

Comment:
**Stale "threadId matching" comment + wrong-chat risk in fallback path**

The comment on the fallback branch (line 309) says _"rely on threadId matching below"_, but there is no such matching anywhere below — the code simply returns the first result (`chats[0].id`). When `userAadObjectId` is absent and the bot has several 1:1 chats (e.g. the bot serves many users), the fallback will silently return the ID of a **different** user's conversation. That means file attachments could be shared to the wrong person's chat.

The two branches at lines 327–334 are also functionally identical — the `chats.length === 1` case is entirely subsumed by `chats.length > 0`, so the first branch adds no value.

Suggested fix — drop the stale comment, collapse the redundant branches, and return `null` when the user is unknown and the list is ambiguous:

```
  // If we have exactly one result (filtered by user), that's our chat.
  if (userAadObjectId && chats.length > 0 && chats[0]?.id) {
    return chats[0].id;
  }

  // Without a user AAD object ID we can't disambiguate multiple 1:1 chats —
  // returning an arbitrary first result risks sharing files to the wrong person.
  if (!userAadObjectId && chats.length === 1 && chats[0]?.id) {
    return chats[0].id;
  }

  return null;
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/msteams/src/send-context.ts
Line: 177-181

Comment:
**Null resolution result not cached — repeated Graph API calls on failure**

When `resolveGraphChatId` returns `null` (Graph API error, empty result, etc.), the `null` is never written back to the conversation store. Because `ref.graphChatId` is typed as `string | undefined` (not nullable), `ref.graphChatId ?? undefined` will evaluate to `undefined` again on the next send, causing another Graph API round-trip every time a file is sent to a conversation that previously failed resolution.

Consider also caching the negative result so that transient-failure retries are bounded:

```suggestion
      // Cache in the conversation store so subsequent sends skip the Graph lookup.
      // Cache even a null result (converted to a sentinel or by storing empty string)
      // so we don't hit the Graph API on every send when resolution consistently fails.
      if (resolved !== null) {
        const store = createMSTeamsConversationStoreFs();
        await store.upsert(conversationId, { ...ref, graphChatId: resolved });
      }
```

Note: if you want to cache the failure, `StoredConversationReference.graphChatId` would need to accept `null | undefined` (or use an explicit sentinel string) — that's a small type change but worthwhile to avoid unbounded Graph API retries.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "fix(msteams): resolv..."

Comment on lines +308 to +334
} else {
// Fallback: list all chats and rely on threadId matching below
path = `/me/chats?$filter=${encodeURIComponent("chatType eq 'oneOnOne'")}&$select=id`;
}

const res = await fetchFn(`${GRAPH_ROOT}${path}`, {
headers: { Authorization: `Bearer ${token}` },
});

if (!res.ok) {
return null;
}

const data = (await res.json()) as {
value?: Array<{ id?: string }>;
};

const chats = data.value ?? [];

// If we have exactly one result (filtered by user), that's our chat.
if (chats.length === 1 && chats[0]?.id) {
return chats[0].id;
}

// If multiple results, return the first one (best-effort for 1:1 with known user).
if (chats.length > 0 && chats[0]?.id) {
return chats[0].id;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Stale "threadId matching" comment + wrong-chat risk in fallback path

The comment on the fallback branch (line 309) says "rely on threadId matching below", but there is no such matching anywhere below — the code simply returns the first result (chats[0].id). When userAadObjectId is absent and the bot has several 1:1 chats (e.g. the bot serves many users), the fallback will silently return the ID of a different user's conversation. That means file attachments could be shared to the wrong person's chat.

The two branches at lines 327–334 are also functionally identical — the chats.length === 1 case is entirely subsumed by chats.length > 0, so the first branch adds no value.

Suggested fix — drop the stale comment, collapse the redundant branches, and return null when the user is unknown and the list is ambiguous:

  // If we have exactly one result (filtered by user), that's our chat.
  if (userAadObjectId && chats.length > 0 && chats[0]?.id) {
    return chats[0].id;
  }

  // Without a user AAD object ID we can't disambiguate multiple 1:1 chats —
  // returning an arbitrary first result risks sharing files to the wrong person.
  if (!userAadObjectId && chats.length === 1 && chats[0]?.id) {
    return chats[0].id;
  }

  return null;
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/graph-upload.ts
Line: 308-334

Comment:
**Stale "threadId matching" comment + wrong-chat risk in fallback path**

The comment on the fallback branch (line 309) says _"rely on threadId matching below"_, but there is no such matching anywhere below — the code simply returns the first result (`chats[0].id`). When `userAadObjectId` is absent and the bot has several 1:1 chats (e.g. the bot serves many users), the fallback will silently return the ID of a **different** user's conversation. That means file attachments could be shared to the wrong person's chat.

The two branches at lines 327–334 are also functionally identical — the `chats.length === 1` case is entirely subsumed by `chats.length > 0`, so the first branch adds no value.

Suggested fix — drop the stale comment, collapse the redundant branches, and return `null` when the user is unknown and the list is ambiguous:

```
  // If we have exactly one result (filtered by user), that's our chat.
  if (userAadObjectId && chats.length > 0 && chats[0]?.id) {
    return chats[0].id;
  }

  // Without a user AAD object ID we can't disambiguate multiple 1:1 chats —
  // returning an arbitrary first result risks sharing files to the wrong person.
  if (!userAadObjectId && chats.length === 1 && chats[0]?.id) {
    return chats[0].id;
  }

  return null;
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread extensions/msteams/src/send-context.ts Outdated
Comment on lines +177 to +181
// Cache in the conversation store so subsequent sends skip the Graph lookup
if (resolved) {
const store = createMSTeamsConversationStoreFs();
await store.upsert(conversationId, { ...ref, graphChatId: resolved });
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Null resolution result not cached — repeated Graph API calls on failure

When resolveGraphChatId returns null (Graph API error, empty result, etc.), the null is never written back to the conversation store. Because ref.graphChatId is typed as string | undefined (not nullable), ref.graphChatId ?? undefined will evaluate to undefined again on the next send, causing another Graph API round-trip every time a file is sent to a conversation that previously failed resolution.

Consider also caching the negative result so that transient-failure retries are bounded:

Suggested change
// Cache in the conversation store so subsequent sends skip the Graph lookup
if (resolved) {
const store = createMSTeamsConversationStoreFs();
await store.upsert(conversationId, { ...ref, graphChatId: resolved });
}
// Cache in the conversation store so subsequent sends skip the Graph lookup.
// Cache even a null result (converted to a sentinel or by storing empty string)
// so we don't hit the Graph API on every send when resolution consistently fails.
if (resolved !== null) {
const store = createMSTeamsConversationStoreFs();
await store.upsert(conversationId, { ...ref, graphChatId: resolved });
}

Note: if you want to cache the failure, StoredConversationReference.graphChatId would need to accept null | undefined (or use an explicit sentinel string) — that's a small type change but worthwhile to avoid unbounded Graph API retries.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/send-context.ts
Line: 177-181

Comment:
**Null resolution result not cached — repeated Graph API calls on failure**

When `resolveGraphChatId` returns `null` (Graph API error, empty result, etc.), the `null` is never written back to the conversation store. Because `ref.graphChatId` is typed as `string | undefined` (not nullable), `ref.graphChatId ?? undefined` will evaluate to `undefined` again on the next send, causing another Graph API round-trip every time a file is sent to a conversation that previously failed resolution.

Consider also caching the negative result so that transient-failure retries are bounded:

```suggestion
      // Cache in the conversation store so subsequent sends skip the Graph lookup.
      // Cache even a null result (converted to a sentinel or by storing empty string)
      // so we don't hit the Graph API on every send when resolution consistently fails.
      if (resolved !== null) {
        const store = createMSTeamsConversationStoreFs();
        await store.upsert(conversationId, { ...ref, graphChatId: resolved });
      }
```

Note: if you want to cache the failure, `StoredConversationReference.graphChatId` would need to accept `null | undefined` (or use an explicit sentinel string) — that's a small type change but worthwhile to avoid unbounded Graph API retries.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd5cb608bf

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread extensions/msteams/src/monitor.ts Outdated
Comment on lines +85 to +88
// Clear the singleton on failure so a future attempt can retry.
activeInstancePromise.catch(() => {
activeInstancePromise = null;
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reset singleton after non-running startup paths

This singleton is only cleared on rejected startup promises, but _startMSTeamsProvider can also resolve successfully with { app: null } when Teams is disabled or credentials are missing. After that first no-op resolve, later invocations (for example after config is fixed during reload) hit the activeInstancePromise guard and keep returning the stale resolved promise, so the provider never attempts to start again until process restart.

Useful? React with 👍 / 👎.

Comment thread extensions/msteams/src/send-context.ts Outdated
Comment on lines +183 to +187
log.debug?.("failed to resolve Graph chat ID", {
conversationId,
error: String(err),
});
graphChatId = null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep resolved chat ID when cache write fails

The try block combines chat ID resolution and cache persistence, so an upsert failure is treated the same as a lookup failure. If resolveGraphChatId succeeds but store.upsert(...) throws (e.g., lock/permission issue), the catch block sets graphChatId to null, causing the current send to fall back to the Bot Framework conversation ID and potentially hit the same Graph chat ID mismatch this change is trying to avoid.

Useful? React with 👍 / 👎.

Fixes openclaw#35822 — Bot Framework conversation.id format is incompatible with
Graph API /chats/{chatId}. Added resolveGraphChatId() to look up the
Graph-native chat ID via GET /me/chats, cached in the conversation store.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sudie-codes sudie-codes force-pushed the fix/msteams-dm-file-chatid branch from bd5cb60 to d852cf2 Compare March 18, 2026 08:16
Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d852cf2fc6

ℹ️ 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".

const encoded = encodeURIComponent(
`chatType eq 'oneOnOne' and members/any(m:m/microsoft.graph.aadUserConversationMember/userId eq '${userAadObjectId}')`,
);
path = `/me/chats?$filter=${encoded}&$select=id`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Query chats with an app-compatible Graph endpoint

resolveGraphChatId builds the lookup request with /me/chats, but this code path is executed from proactive send context where the token comes from MsalTokenProvider(authConfig) built from app credentials (client ID/secret), i.e. application permission flow. Microsoft Graph treats /me/chats as delegated-only, so app-only deployments get a non-OK response here, graphChatId resolves to null, and sending falls back to the original Bot Framework conversation ID that this commit is trying to avoid for DM file uploads.

Useful? React with 👍 / 👎.

@BradGroux BradGroux self-assigned this Mar 20, 2026
Copy link
Copy Markdown
Member

@BradGroux BradGroux left a comment

Choose a reason for hiding this comment

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

Best implementation of resolveGraphChatId across the overlapping PRs:

  • Safe ambiguity handling (returns null when no userAadObjectId + multiple chats)
  • Proper caching strategy (only caches success, transient failures retried)
  • Warning log on resolution failure
  • Comprehensive test coverage (5 resolution tests + 2 propagation tests)

Two minor notes:

  1. userAadObjectId is interpolated directly into OData $filter without UUID format validation. Low risk (server-side data from Bot Framework), but a defense-in-depth regex check would be cheap insurance.
  2. Chat.Read/Chat.ReadBasic permission requirement could be surfaced more clearly — users without it will get silent null fallbacks.

✅ Approve. Merge before #49580 and #49587 which duplicate this code.

@BradGroux BradGroux merged commit 06845a1 into openclaw:main Mar 20, 2026
32 of 41 checks passed
frankekn pushed a commit to artwalker/openclaw that referenced this pull request Mar 23, 2026
…#49585)

Fixes openclaw#35822 — Bot Framework conversation.id format is incompatible with
Graph API /chats/{chatId}. Added resolveGraphChatId() to look up the
Graph-native chat ID via GET /me/chats, cached in the conversation store.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 24, 2026
…#49585)

Fixes openclaw#35822 — Bot Framework conversation.id format is incompatible with
Graph API /chats/{chatId}. Added resolveGraphChatId() to look up the
Graph-native chat ID via GET /me/chats, cached in the conversation store.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
(cherry picked from commit 06845a1)
alexey-pelykh added a commit to remoteclaw/remoteclaw that referenced this pull request Mar 24, 2026
* fix(msteams): resolve Graph API chat ID for DM file uploads (openclaw#49585)

Fixes openclaw#35822 — Bot Framework conversation.id format is incompatible with
Graph API /chats/{chatId}. Added resolveGraphChatId() to look up the
Graph-native chat ID via GET /me/chats, cached in the conversation store.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
(cherry picked from commit 06845a1)

* test: fix fetch mock typing

(cherry picked from commit 0f43dc4)

* fix: restore repo-wide gate after upstream sync

(cherry picked from commit 14074d3)

* test(msteams): align adapter doubles with interfaces

(cherry picked from commit 5b7ae24)

* test: tighten msteams regression assertions

(cherry picked from commit c8a36c6)

* test: dedupe msteams attachment redirects

(cherry picked from commit 017c0dc)

* MSTeams: move outbound session routing behind plugin boundary

(cherry picked from commit 028f3c4)

* fix: remove session-route.ts — depends on missing upstream infrastructure

* test(msteams): cover graph helpers

(cherry picked from commit 1ea2593)

* fix(test): split msteams attachment helpers

(cherry picked from commit 23c8af3)

* test: share directory runtime helpers

(cherry picked from commit 38b0986)

* fix: stabilize build dependency resolution (openclaw#49928)

* build: mirror uuid for msteams

Add uuid to both the msteams bundled extension and the root package so the workspace build can resolve @microsoft/agents-hosting during tsdown while standalone extension installs also have the runtime dependency available.

Regeneration-Prompt: |
  pnpm build failed because @microsoft/agents-hosting 1.3.1 requires uuid in its published JS but does not declare it in its package manifest. The msteams extension dynamically imports that package, and the workspace build resolves it from the root dependency graph. Mirror uuid into the root package for workspace builds and keep it in extensions/msteams/package.json so standalone plugin installs also resolve it. Update the lockfile to match the manifest changes.

* build: prune stale plugin dist symlinks

Remove stale dist and dist-runtime plugin node_modules symlinks before tsdown runs. These links point back into extension installs, and tsdown's clean step can traverse them on rebuilds and hollow out the active pnpm dependency tree before plugin-sdk declaration generation runs.

Regeneration-Prompt: |
  pnpm build was intermittently failing in the plugin-sdk:dts phase after earlier build steps had already run. The symptom looked like missing root packages such as zod, ajv, commander, and undici even though a fresh install briefly fixed the problem. Investigate the build pipeline step by step rather than patching TypeScript errors. Confirm whether rebuilds mutate node_modules, identify the first step that does it, and preserve existing runtime-postbuild behavior.
  The key constraint is that dist and dist-runtime plugin node_modules links are intentional for runtime packaging, so do not remove that feature globally. Instead, make rebuilds safe by deleting only stale symlinks left in generated output before invoking tsdown, so tsdown cleanup cannot recurse back into the live pnpm install tree. Verify with repeated pnpm build runs.
(cherry picked from commit 505d140)

* test(msteams): cover store and live directory helpers

(cherry picked from commit 55e0c63)

* test(msteams): cover setup wizard status

(cherry picked from commit 653d69e)

* test: tighten msteams regression assertions

(cherry picked from commit 689a734)

* refactor: share teams drive upload flow

(cherry picked from commit 6b04ab1)

* test(msteams): cover routing and setup

(cherry picked from commit 774a206)

* msteams: extend MSTeamsAdapter and MSTeamsActivityHandler types; implement self() (openclaw#49929)

- Add updateActivity/deleteActivity to MSTeamsAdapter
- Add onReactionsAdded/onReactionsRemoved to MSTeamsActivityHandler
- Implement directory self() to return bot identity from appId credential
- Add tests for self() in channel.directory.test.ts

(cherry picked from commit 7c3af37)

* test(msteams): cover upload and webhook helpers

(cherry picked from commit 7d11f6c)

* msteams: fix sender allowlist bypass when route allowlist is configured (GHSA-g7cr-9h7q-4qxq) (openclaw#49582)

When a route-level (teams/channel) allowlist was configured but the sender
allowlist (allowFrom/groupAllowFrom) was empty, resolveSenderScopedGroupPolicy
would downgrade the effective group policy from "allowlist" to "open", allowing
any Teams user to interact with the bot.

The fix: when channelGate.allowlistConfigured is true and effectiveGroupAllowFrom
is empty, preserve the configured groupPolicy ("allowlist") rather than letting
it be downgraded to "open". This ensures an empty sender allowlist with an active
route allowlist means deny-all rather than allow-all.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
(cherry picked from commit 897cda7)

* fix(msteams): batch multi-block replies into single continueConversation call (openclaw#29379) (openclaw#49587)

Teams silently drops blocks 2+ when each deliver() opens its own
continueConversation() call. Accumulate rendered messages across all
deliver() calls and flush them together in markDispatchIdle().

On batch failure, retry each message individually so trailing blocks
are not silently lost. Log a warning when any individual messages fail
so flush failures are visible in logs.

(cherry picked from commit 8b5eeba)

* test(msteams): cover poll and file-card helpers

(cherry picked from commit 8ff277d)

* test: dedupe msteams consent auth fixtures

(cherry picked from commit a9d8518)

* refactor: share dual text command gating

(cherry picked from commit b61bc49)

* test: share msteams safe fetch assertions

(cherry picked from commit d4d0091)

* MSTeams: lazy-load runtime-heavy channel paths

(cherry picked from commit da4f825)

* fix(msteams): isolate probe test env credentials

(cherry picked from commit e9078b3)

* test: dedupe msteams policy route fixtures

(cherry picked from commit f2300f4)

* fix: fix remaining openclaw references in cherry-picked msteams files

* fix: adapt cherry-picks for fork TS strictness

* fix: revert cross-cutting refactors, keep msteams-specific changes only

* fix: format cherry-picked files with oxfmt

---------

Co-authored-by: sudie-codes <suvenkat95@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
Co-authored-by: Josh Lehman <josh@martian.engineering>
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…#49585)

Fixes openclaw#35822 — Bot Framework conversation.id format is incompatible with
Graph API /chats/{chatId}. Added resolveGraphChatId() to look up the
Graph-native chat ID via GET /me/chats, cached in the conversation store.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…#49585)

Fixes openclaw#35822 — Bot Framework conversation.id format is incompatible with
Graph API /chats/{chatId}. Added resolveGraphChatId() to look up the
Graph-native chat ID via GET /me/chats, cached in the conversation store.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…#49585)

Fixes openclaw#35822 — Bot Framework conversation.id format is incompatible with
Graph API /chats/{chatId}. Added resolveGraphChatId() to look up the
Graph-native chat ID via GET /me/chats, cached in the conversation store.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MS Teams DM file attachments fail: Graph API chatId format mismatch and messageId encoding error

2 participants