Skip to content

th-df7007/th-e7ef23: stop swallowing the exception behind INTERNAL_ERROR; fix .NET gateway env vars - #336

Merged
brentrager merged 2 commits into
mainfrom
th-df7007-dotnet-error
Aug 8, 2026
Merged

th-df7007/th-e7ef23: stop swallowing the exception behind INTERNAL_ERROR; fix .NET gateway env vars#336
brentrager merged 2 commits into
mainfrom
th-df7007-dotnet-error

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

Running the smooth-bench agentic suite across all five engines, the .NET server booted fine, accepted the WebSocket upgrade (HTTP 101 on /ws), and then returned INTERNAL_ERROR: Internal error processing the request. for every send_message. Zero tool calls, every scenario INCONCLUSIVE.

Worse: the .NET server logged nothing about the failure. No exception, no stack, at any level. Both places FrameDispatcher maps an exception to INTERNAL_ERROR discarded it outright.

Fix 1 — observability (th-e7ef23)

FrameDispatcher already carried an ILogger? (wired from DI at SmoothOperatorWebSocketExtensions.cs:107) but never used it at either catch site. Both now route through LogInternalError, which records action + requestId + the exception with its stack at Error level, falling back to stderr when no logger is wired. The wire message is unchanged — still generic, still leaks nothing to the client.

Same swallow existed in the siblings: the Go dispatcher dropped err at all seven of its INTERNAL_ERROR sites (now one internalError chokepoint that slog.Errors first), and the Python dispatcher dropped the exception at both of its own (now logging.exception). TypeScript already logged via console.error; Rust puts the detail on the wire.

Fix 2 — root cause (th-df7007)

The new logging named it on the first run:

INTERNAL_ERROR handling action 'send_message' (requestId r2): HTTP 401 (auth_error: 401)
LiteLLM Virtual Key expected. Received=****, expected to start with 'sk-'.

dotnet/server/host/Program.cs read SMOOTH_GATEWAY_URL / SMOOTH_GATEWAY_KEY / SMOOTH_MODEL. Rust, Go, TS and Python all read the SMOOAI_* spelling — the actual cross-engine contract. Every launcher and bench exporting the shared names left the .NET host keyless, so it called the gateway with the literal key "unset". SMOOAI_* now wins; SMOOTH_* kept as a fallback for existing deployments. README + Dockerfile updated.

Verification

Before (bench, no-over-refusal, deepseek-v4-flash, host isolation): INCONCLUSIVE, INTERNAL_ERROR, 0 tool calls.

After: /health reports deepseek-v4-flash (was silently claude-haiku-4-5), turns stream tokens, and the bench scenario runs conclusively with real tool calls (read_file, bash, list_files, grep). Zero INTERNAL_ERROR across every scenario's engine log.

Tests

  • dotnet/server/tests/InternalErrorLoggingTests.cs — 3 tests: turn-path logging, dispatcher-path logging, and the no-logger fallback. Asserts the generic message on the wire AND the detail in the log.
  • go/server/internal_error_log_test.go — same contract over slog.
  • python/server/tests/test_internal_error_logging.py — same contract over caplog.

Full suites green: .NET 294 passed, Go all packages ok, Python 231 passed / 1 skipped.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P83HrAKaqp2ehxQcW7m6QU

brentrager and others added 2 commits August 8, 2026 08:59
…st reads SMOOAI_* gateway vars

The .NET server returned INTERNAL_ERROR on every send_message and logged
nothing at all about why — both catch sites in FrameDispatcher discarded the
exception. Route them through LogInternalError (action + requestId + stack at
Error level, stderr fallback with no ILogger); the wire message stays generic.

That logging immediately named the cause: HTTP 401 'LiteLLM Virtual Key
expected. Received=****'. The host read SMOOTH_GATEWAY_URL/KEY/MODEL while
rust, go, ts and python all read the SMOOAI_* spelling, so every launcher and
bench that exports the shared contract left the .NET host keyless. Read
SMOOAI_* first, keep SMOOTH_* as a fallback.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P83HrAKaqp2ehxQcW7m6QU
…ERROR too

Same class of bug as the .NET fix: the Go dispatcher dropped err at all seven
of its INTERNAL_ERROR sites, the Python dispatcher dropped the exception at
both of its own. Go now routes them through one internalError chokepoint that
slog.Errors first; Python logs with logging.exception. Wire messages unchanged
(still generic, still leak nothing). TypeScript already logged; Rust puts the
detail on the wire.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P83HrAKaqp2ehxQcW7m6QU
@changeset-bot

changeset-bot Bot commented Aug 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bcfe4a9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@smooai/smooth-operator Patch
@smooai/smooth-operator-web-chat-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@brentrager
brentrager merged commit f6a7652 into main Aug 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant