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
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 JWTssmba.trafficmanager.net— needed by the Bot Framework Connector when the bot replies to messagesNeither 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.comwas blocked, every inbound webhook returned401 Unauthorized. That on its own is somewhat expected — but the problem is that401also covers:appId/appPasswordThere is no way to tell them apart at
infolog level. The actual DNS / TLS error from the JWKS fetch is absorbed insidecreateBotFrameworkJwtValidator(sdk.ts):The middleware in
monitor.tsthen logs"JWT validation failed"atdebugand sends401. So at default log levels, all you see is401. You don't see a JWKS fetch timeout, you don't see anENOTFOUND, nothing. It looks like a credentials problem.When
smba.trafficmanager.netwas blocked the bot received messages fine (JWT validation was now passing), but replies never arrived. The outbound send errors inerrors.tsclassify transport-level failures (connection refused, DNS failure, timeout) as"unknown", and"unknown"gets no hint message fromformatMSTeamsSendErrorHint. 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.comwas blocked, every inbound webhook returned401 Unauthorized. That on its own is somewhat expected — but the problem is that401also covers:appId/appPasswordThere is no way to tell them apart because the actual DNS / TLS error from the JWKS fetch is absorbed inside
createBotFrameworkJwtValidator(sdk.ts):The middleware in
monitor.tsthen sends401. All you see is401— no JWKS fetch timeout, noENOTFOUND, nothing. It looks like a credentials problem.When
smba.trafficmanager.netwas blocked the bot received messages fine (JWT validation was now passing), but replies never arrived. The outbound send errors inerrors.tsclassify transport-level failures (connection refused, DNS failure, timeout) as"unknown", and"unknown"gets no hint message fromformatMSTeamsSendErrorHint. 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,createBotFrameworkJwtValidatorThe inner
catcharoundgetSigningKey+jwt.verifyreturnsfalsefor everything — a bad signature, an expired token, or a firewall blockinglogin.botframework.com:443. They're all the same from the caller's perspective.Invoke fast-ack swallows handler errors —
sdk.ts,adapter.processFor
invokeactivities,200is flushed beforeawait logic(context)runs (this is correct Teams behaviour to avoid invoke timeouts). But if the handler throws after that, thecatchblock skips the500response (if (!isInvoke) { response.status(500).send(...) }). The failure lands inruntime.erroronly. Teams already considers the invoke handled.Message handler failures hidden behind optional chaining —
monitor-handler.tsdeps.runtime.error?.is optional. Ifruntime.erroris not provided, a thrown error fromhandleTeamsMessagedisappears entirely. The pipeline also always callsnext(), so from the outside the handler looks like it completed normally.The same pattern applies to
onReactionsAddedandonReactionsRemoved.Allowlist hydration errors swallowed —
monitor.tsAt 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,formatMSTeamsSendErrorHintThe classifier handles
auth,throttled, andtransient(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 tosmba, egress rules, or TLS inspection.Target resolver errors reported but hook is optional —
channel.tsSame 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