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

Open
#212 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.