Skip to content

chat/send: surface 502 REPLY_FAILED when VA is missing api_key/provider/model#190

Merged
jeffdafoe merged 1 commit into
mainfrom
chat-send-throw-on-missing-va-config
May 10, 2026
Merged

chat/send: surface 502 REPLY_FAILED when VA is missing api_key/provider/model#190
jeffdafoe merged 1 commit into
mainfrom
chat-send-throw-on-missing-va-config

Conversation

@jeffdafoe
Copy link
Copy Markdown
Owner

Symptom

A salem-visitor VA was created in agent_configuration with provider=anthropic and model=claude-haiku-4-5 but no api_key. The Salem engine spawned visitors normally — they walked from a village-edge tile to the tavern and entered an outdoor huddle — but every subsequent agent tick failed silently:

```
agent-tick Caleb Wendell the wool-buyer iter=0: chat response missing reply
(body: {"from_agent":"salem-engine","to_agents":[{...,"agent":"salem-visitor",...}],"reply":null})
```

Result: visitors froze at (1488, 656) outside the tavern for hours until `visitor_expires_at` cleared them. No error code surfaced to the engine, no admin signal, no retry — just `reply: null` on HTTP 200.

Cause

`handleDirectChat` in `node/api/src/services/virtual-agent.js` returned `null` when the target VA was missing `api_key`, `provider`, or `model`. The chat/send wait-mode path (`routes/chat.js:148-158`) awaits `pendingReplyPromise`, gets `null`, and returns `{ reply: null }` with HTTP 200. Engine sees a successful response with no payload and gives up — same shape it would see if the VA legitimately chose not to reply.

Compare with the existing `decryptApiKey()` failure path: a malformed encrypted key throws, the wait-mode `catch (replyErr)` block at `routes/chat.js:159` maps it to `502 REPLY_FAILED { code, message }`, and the engine logs the failure visibly.

Fix

Throw instead of returning null on missing config. The existing wait-mode catch already produces the right error shape — no route changes needed.

Blast radius

  • wait=true callers (Salem engine): get `502 REPLY_FAILED { message: 'Agent salem-visitor missing provider/model/api_key' }`. Identical shape to the encrypted-key error, which the engine already handles.
  • non-wait callers: `services/chat.js:403` and `:411` already swallow rejections with `.catch(() => {})`. No behavior change.
  • Other call sites of `handleDirectChat`: only `services/chat.js` calls it. Both branches handle rejection.
  • `!agent || !agent.virtual` guard at line 2095: unchanged. That case (sending to a human or a missing actor) is filtered upstream by the route's wait=true precondition.

Test plan

  • Configure a VA with `provider`/`model` set but `api_key=NULL`, send chat with `wait=true`, expect `502 REPLY_FAILED` (not HTTP 200 with `reply: null`).
  • Configure a VA with a malformed encrypted key — verify existing `Invalid encrypted API key format` path still returns the same `502 REPLY_FAILED` shape (no regression).
  • Send chat to a properly-configured VA in wait mode — verify normal `{ reply: {...} }` response (no regression on the happy path).
  • After deploy + setting the salem-visitor api_key, spawn a visitor and verify they no longer freeze at the tavern (the original symptom).

— Home

…er/model

handleDirectChat returned null when the target VA had no api_key,
provider, or model. wait=true callers (the Salem engine) saw
HTTP 200 with reply: null and could not distinguish a misconfigured
VA from one that simply chose not to respond. Visitors with the
salem-visitor template (created without an api_key) froze on
arrival at the tavern and sat there until visitor_expires_at; the
engine logged 'chat response missing reply' on every tick but
emitted no error code, so the failure was invisible to operators
without grepping the engine journal.

Throw instead. The chat/send route's existing wait-mode catch
(routes/chat.js:159) maps thrown errors to 502 REPLY_FAILED with
the message text — the same shape the encrypted-key failure path
already produces. Non-wait chatSend callers in services/chat.js
already swallow rejections via .catch(() => {}).
@jeffdafoe jeffdafoe merged commit d8b5928 into main May 10, 2026
6 checks passed
@jeffdafoe jeffdafoe deleted the chat-send-throw-on-missing-va-config branch May 10, 2026 15:33
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