Skip to content

[Bug]: MS Teams bot fails silently when network paths are blocked — errors are swallowed and logs don't help #77674

@david-garcia-garcia

Description

@david-garcia-garcia

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

We had to open two firewall egress rules to get the Teams bot working:

  • login.botframework.com — needed to fetch the JWKS keys for validating inbound Bot Framework JWTs
  • smba.trafficmanager.net — needed by the Bot Framework Connector when the bot replies to messages

Neither of these hosts is documented in the Teams channel setup, and when either one is blocked the bot fails in ways that are genuinely hard to trace. This is a request to make those failure modes visible.

Steps to reproduce

When login.botframework.com was blocked, every inbound webhook returned 401 Unauthorized. That on its own is somewhat expected — but the problem is that 401 also covers:

  • a misconfigured appId / appPassword
  • an expired or malformed token sent by Teams
  • a clock skew / expiry issue
  • a JWKS fetch failure (our case — firewall)

There is no way to tell them apart at info log level. The actual DNS / TLS error from the JWKS fetch is absorbed inside createBotFrameworkJwtValidator (sdk.ts):

} catch {
  return false;   // <-- DNS error, ECONNREFUSED, TLS issue — all collapsed to false
}

The middleware in monitor.ts then logs "JWT validation failed" at debug and sends 401. So at default log levels, all you see is 401. You don't see a JWKS fetch timeout, you don't see an ENOTFOUND, nothing. It looks like a credentials problem.

When smba.trafficmanager.net was blocked the bot received messages fine (JWT validation was now passing), but replies never arrived. The outbound send errors in errors.ts classify transport-level failures (connection refused, DNS failure, timeout) as "unknown", and "unknown" gets no hint message from formatMSTeamsSendErrorHint. So the log shows the reply failed, but gives no pointer to egress or the Connector endpoint.

Expected behavior

Why diagnosis was painful

When login.botframework.com was blocked, every inbound webhook returned 401 Unauthorized. That on its own is somewhat expected — but the problem is that 401 also covers:

  • a misconfigured appId / appPassword
  • an expired or malformed token sent by Teams
  • a clock skew / expiry issue
  • a JWKS fetch failure (our case — firewall)

There is no way to tell them apart because the actual DNS / TLS error from the JWKS fetch is absorbed inside createBotFrameworkJwtValidator (sdk.ts):

} catch {
  return false;   // <-- DNS error, ECONNREFUSED, TLS issue — all collapsed to false
}

The middleware in monitor.ts then sends 401. All you see is 401 — no JWKS fetch timeout, no ENOTFOUND, nothing. It looks like a credentials problem.

When smba.trafficmanager.net was blocked the bot received messages fine (JWT validation was now passing), but replies never arrived. The outbound send errors in errors.ts classify transport-level failures (connection refused, DNS failure, timeout) as "unknown", and "unknown" gets no hint message from formatMSTeamsSendErrorHint. So the log shows the reply failed, but gives no pointer to egress or the Connector endpoint.


Code locations where errors disappear or get too quiet

JWT key fetch swallowed — sdk.ts, createBotFrameworkJwtValidator

The inner catch around getSigningKey + jwt.verify returns false for everything — a bad signature, an expired token, or a firewall blocking login.botframework.com:443. They're all the same from the caller's perspective.

Invoke fast-ack swallows handler errors — sdk.ts, adapter.process

For invoke activities, 200 is flushed before await logic(context) runs (this is correct Teams behaviour to avoid invoke timeouts). But if the handler throws after that, the catch block skips the 500 response (if (!isInvoke) { response.status(500).send(...) }). The failure lands in runtime.error only. Teams already considers the invoke handled.

Message handler failures hidden behind optional chaining — monitor-handler.ts

handler.onMessage(async (context, next) => {
  try {
    await handleTeamsMessage(context as MSTeamsTurnContext);
  } catch (err) {
    deps.runtime.error?.(`msteams handler failed: ${formatUnknownError(err)}`);
  }
  await next();
});

deps.runtime.error?. is optional. If runtime.error is not provided, a thrown error from handleTeamsMessage disappears entirely. The pipeline also always calls next(), so from the outside the handler looks like it completed normally.

The same pattern applies to onReactionsAdded and onReactionsRemoved.

Allowlist hydration errors swallowed — monitor.ts

At startup, allowlist entries are resolved against Graph. If Graph calls fail, the bot falls back to raw config entries and logs the error at runtime.log?. level — which is also optional, meaning it can silently vanish if the runtime hook isn't wired.

Outbound failures get no network hint — errors.ts, formatMSTeamsSendErrorHint

The classifier handles auth, throttled, and transient (5xx) with actionable strings. Transport-level errors — ECONNREFUSED, ENOTFOUND, TLS handshake failures toward the Connector — fall into "unknown" and get no hint at all. There is nothing pointing to smba, egress rules, or TLS inspection.

Target resolver errors reported but hook is optional — channel.ts

} catch (err) {
  runtime.error?.(`msteams resolve failed: ${String(err)}`);
  markPendingLookupFailed(pending);
}

Same pattern: optional hook, so depending on runtime setup the error may not surface.

Actual behavior

Swallowed or confusing error details

OpenClaw version

2026.5.3

Operating system

Ubuntu 24.0.

Install method

No response

Model

N/A

Provider / routing chain

N/A

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Impact and severity

No response

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingbug:behaviorIncorrect behavior without a crash

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions