aws / aws/bedrock-agentcore-sdk-typescript
BedrockAgentCoreApp throws FST_ERR_REP_INVALID_PAYLOAD_TYPE when a non-streaming handler throws and the client sent Accept: text/event-stream
- Dominant language
- TypeScript
- Stars
- 91
- Forks
- 31
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 5
Description
**Describe the bug**
When a client sends `Accept: text/event-stream` on a `{ sse: true }` invocation route, but the `invocationHandler.process` throws *before* ever streaming anything (i.e. it's a plain async function, not an async generator, or an async generator that throws before its first `yield`), the request fails with a confusing `FST_ERR_REP_INVALID_PAYLOAD_TYPE` instead of the expected HTTP 500 with the real error message.
This happens because `@fastify/sse@0.4.0` (the version currently pinned in `package.json`) eagerly calls `reply.raw.setHeader('Content-Type', 'text/event-stream')` as soon as the `Accept` header admits SSE — before the route handler runs at all, regardless of whether the handler ends up streaming. When `_handleInvocation`'s catch block then calls `reply.status(500).send({ error: errorMessage })`, Fastify refuses to JSON-serialize the plain object against a response whose content-type is already pinned to a non-JSON value, and throws `FST_ERR_REP_INVALID_PAYLOAD_TYPE` instead. The original error message is lost — the caller only sees the secondary serialization crash.
This is a distinct scenario from #66 / the fix in #118: that fix (correctly) guards the case where SSE streaming has already flushed headers via `writeHead(200)` (`reply.raw.headersSent === true`). Here, `headersSent` is still `false` — only the header *value* has been staged via `setHeader`, not actually sent — so that guard doesn't trigger, and the plain non-streaming `else` branch calls `reply.send()` straight into the corrupted content-type.
We hit this in production running an agent on Bedrock AgentCore Runtime: the invocation failed with a generic 500 and no indication of the real cause (in our case, a downstream database error), because the real error was swallowed by this secondary Fastify crash.
**To Reproduce**
Steps to reproduce the behavior:
1. Install package: `npm install bedrock-agentcore`.
2. Create a server using the code sample below and run it.
3. Send `POST /invocations` with header `Accept: text/event-stream` and a session id header, to a handler that throws synchronously (not an async generator).
4. See a 500 response, but the response body/logs show `FST_ERR_REP_INVALID_PAYLOAD_TYPE` ("Attempted to send payload of invalid type 'object'. Expected a string or Buffer.") instead of the handler's actual error message.
**Expected behavior**
The response should be a normal HTTP 500 with a JSON body `{ "error": "" }` — exactly as it already is when the client does *not* send `Accept: text/event-stream`.
**Code Sample**
```typescript
import { BedrockAgentCoreApp } from 'bedrock-agentcore/runtime';
const app = new BedrockAgentCoreApp({
invocationHandler: {
process: async (_request, _context) => {
throw new Error('downstream dependency failed');
},
},
});
app.run();
```
```bash
curl -X POST http://localhost:8080/invocations \
-H 'Content-Type: application/json' \
-H 'Accept: text/event-stream' \
-H 'x-amzn-bedrock-agentcore-runtime-session-id: test-session' \
-d '{}'
```
**Error Output**
```
FastifyError: Attempted to send payload of invalid type 'object'. Expected a string or Buffer.
at onSendEnd (node_modules/fastify/lib/reply.js:674:11)
at onSendHook (node_modules/fastify/lib/reply.js:561:5)
at Reply.send (node_modules/fastify/lib/reply.js:232:3)
at BedrockAgentCoreApp._handleInvocation (node_modules/bedrock-agentcore/dist/src/runtime/app.js:371:41)
at async Object.sseHandler (node_modules/@fastify/sse/index.js:437:15)
{"code":"FST_ERR_REP_INVALID_PAYLOAD_TYPE","statusCode":500}
```
**Environment (please complete the following information):**
- OS: macOS 14 / Amazon Linux (AWS Bedrock AgentCore Runtime container)
- Node.js version: 22.x
- Package version: 0.3.0 and 0.4.0 (confirmed via source diff — `src/runtime/app.ts` is byte-identical between the two)
- AWS SDK version: n/a (client-side bug, no AWS SDK call involved)
**Additional context**
`@fastify/sse@0.5.0` ([fastify/sse#42](https://github.com/fastify/sse/pull/42), resolving [fastify/sse#16](https://github.com/fastify/sse/issues/16)) fixes the root cause upstream: SSE response headers are now committed lazily, only when `SSEContext.sendHeaders()` is actually invoked on the first real `send()`/`stream()` call. For `{ sse: true }` ("legacy") routes, a handler that throws (or otherwise never streams) now falls through to Fastify's normal JSON serialization untouched — no changes needed to this repo's own `src/runtime/app.ts`.
**Possible Solution**
Bump the `@fastify/sse` dependency from `^0.4.0` to `^0.5.0` in `package.json`. I've verified this fixes the issue with an integration test (real Fastify + `@fastify/sse`, no mocking) that fails with the exact error above against `0.4.0` and passes against `0.5.0`. PR incoming.
Contributor guide
Research direction
Start with the pinned @fastify/sse dependency in package.json and the described integration test using a non-streaming throwing handler and an Accept: text/event-stream request. Verify the failure against version 0.4.0 and the expected normal JSON 500 behavior with version 0.5.0; the issue is done when the handler's original error message is preserved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- nodejs, typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100