# \[Bug\] Dev harness fails at 64 KiB despite the documented ~4 MiB invoke result limit: bridge crashes and misleading Executa exit errors

**URL:** <https://forum.anna.partners/t/bug-dev-harness-fails-at-64-kib-despite-the-documented-4-mib-invoke-result-limit-bridge-crashes-and-misleading-executa-exit-errors/338>\
**Category:** Developers\
**Created:** [September 23, 2026, 6:10am UTC](https://forum.anna.partners/t/bug-dev-harness-fails-at-64-kib-despite-the-documented-4-mib-invoke-result-limit-bridge-crashes-and-misleading-executa-exit-errors/338 "2026-09-23T06:10:38Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![HappyLight](https://avatars.discourse-cdn.com/v4/letter/h/7feea3/32.png) [@HappyLight](https://forum.anna.partners/u/HappyLight)\
**Post date:** [September 23, 2026, 6:10am UTC](https://forum.anna.partners/t/bug-dev-harness-fails-at-64-kib-despite-the-documented-4-mib-invoke-result-limit-bridge-crashes-and-misleading-executa-exit-errors/338/1 "2026-09-23T06:10:38Z")

</div>

While developing PPT App, we found that the local `anna-app dev` harness still behaves differently from the published size contract for synchronous `tools.invoke` results.

**Protocol frames larger than 64 KiB cause serious failures: large requests terminate the Python bridge process, preventing even subsequent small requests from running; large responses crash the plugin response reader task and incorrectly report `executa process exited`.** Neither case returns a size-limit error that the application can handle.

Using an isolated minimal plugin, we reproduced these failures in the runtimes pinned by both our project’s current CLI and the latest CLI available as of September 23, 2026. We traced them to two asyncio readers using the default 64 KiB limit. We would appreciate confirmation and alignment of the platform and local harness transport contracts, along with fixes for the process and task lifecycle failures caused by oversized frames.

## Background: the published synchronous result limit is approximately 4 MiB

We previously reported the platform’s silent truncation of `tools.invoke` results in this thread:

[Platform invoke responses are silently truncated at 32,000 characters, corrupting application data and behaving differently from the local dev harness](https://forum.anna.partners/t/platform-invoke-responses-are-silently-truncated-at-32-000-characters-corrupting-application-data-and-behaving-differently-from-the-local-dev-harness/320)

The official reply confirmed that the truncation issue had been fixed and explicitly stated a **4 MiB** limit for the full synchronous `tools.invoke` result, with `result_too_large` returned when the limit is exceeded.

The current [“Result size and integrity” section of the App UI Host API documentation](https://staging.anna.partners/developers/apps/app-ui-host-api.md) further states:

> **~4 MiB** for the full serialized result (payload + envelopes), bounded by the NATS transport `max_payload` (4 194 304 bytes)

The same section also explicitly states:

> There is **no** intermediate per-string-field limit — payloads such as a 100 KB HTML document or a 200 KB base64 data URL pass through unchanged as long as the whole result fits the channel cap.

Our understanding is that **a synchronous invoke result, including its protocol envelopes, should be returned intact when it fits within the approximately 4 MiB channel budget. Exceeding that budget should produce an explicit, machine-readable error.** The local development environment should support the same contract; otherwise, local testing cannot validate data transfers that the platform permits.

Several distinct limits need to be kept separate:

| Scope | Limit in the current official documentation |
| --- | --- |
| Full synchronous `tools.invoke` result, including envelopes | Approximately 4 MiB |
| `tools.invokeAsync` result | 256 KB |
| `tools.invokeAsync` arguments | 64 KB |
| A single APS KV value | 64 KB by default / advisory; the hard cap depends on environment configuration |

The last two entries are documented in the [tools.\* detailed reference](https://staging.anna.partners/developers/reference/host-api-tools.md) and the [APS documentation](https://staging.anna.partners/developers/tools/executa-storage.md), respectively. They should not be interpreted as a universal 64 KB limit on synchronous invoke results.

We are not inferring that synchronous request arguments support 4 MiB merely because synchronous results do. **We would also appreciate a separate, explicit size contract for synchronous request arguments.** Whatever that request limit is, exceeding it should not terminate the entire bridge.

## Test environment and methodology

- Operating system: macOS.
- Node.js: v24.19.0.
- Python: 3.10.15.
- Test date: September 23, 2026.

| CLI version | Pinned `anna-app-runtime-local` | `anna-app-core` in the test environment |
| --- | --- | --- |
| 0.1.47, currently used by our project | 0.2.0a21 | 0.18.1 |
| 0.1.53, the latest npm release when checked | 0.2.0a23 | 0.20.0 |

Both version combinations reproduced the issues below.

To isolate the failures from PPT application logic, we used a standalone plugin that only exchanges valid JSON-RPC and returns synthetic ASCII content. We tested:

1. The published package’s `ExecutaPool`, inspecting the response reader task, child process exit status, and plugin PID on subsequent calls.
2. The published package’s native Python bridge, using `executas.register` → `session.create` → `session.call(ns="tools", method="invoke")` to exercise the actual dispatcher and error mapping.
3. `PythonBridge` from both published CLI versions, verifying the Node-side errors and subsequent call behavior after the bridge exits.

These tests used the components the harness actually depends on, without PPT rendering, network uploads, or APS. We did not test the remote platform’s actual 4 MiB boundary in this investigation. This report concerns independently reproducible local transport failures and their inconsistency with the published contract.

## Issue 1: large requests terminate the Python bridge and break subsequent calls

When a complete JSON-RPC request line sent from Node to the Python bridge exceeds 65,536 bytes, the bridge exits with code `1` instead of returning a structured error.

All sizes below are **the UTF-8 byte length of the complete JSON-RPC line, excluding the trailing LF newline** :

| Request frame size | Observed result |
| --- | --- |
| 65,535 bytes | Succeeds; subsequent ping and small invoke calls work |
| 65,536 bytes | Succeeds; subsequent ping and small invoke calls work |
| 65,537 bytes | No JSON-RPC response; bridge exits with code 1 |
| 102,400 bytes | Also terminates the bridge |

The actual CLI-side error is:

```plaintext
python bridge exited (code=1 signal=-)

```

Even a very small subsequent request then fails with:

```plaintext
python bridge not running

```

**The failure affects more than the current tool call: once the shared bridge is unavailable, subsequent calls cannot proceed either.** We observed no automatic recovery in these tests.

The boundary applies to the entire protocol frame, not just the application string. In our minimal request, a 65,536-byte frame contained only 65,331 bytes of content; the remainder consisted of session, method, argument, and JSON envelope fields. Application content can therefore hit this boundary before reaching 64 KiB itself.

### Identified cause

The non-Windows branch of `_iter_stdin_lines()` in `anna_app_runtime_local/bridge.py` uses:

```python
reader = asyncio.StreamReader()
protocol = asyncio.StreamReaderProtocol(reader)
await loop.connect_read_pipe(lambda: protocol, sys.stdin)
while True:
    line = await reader.readline()

```

No explicit `limit` is set, so asyncio’s default 65,536-byte limit applies. An oversized line triggers `LimitOverrunError`, which `readline()` then converts into:

```plaintext
ValueError: Separator is found, but chunk is longer than limit

```

The exception propagates out of the stdin reader loop without being converted into a protocol error, ultimately terminating the bridge. The Node side marks the bridge as closed and continues rejecting calls.

We tested this on macOS. Linux uses the same source branch but has not been tested here. Windows has a different stdin implementation, so these findings do not establish that the request-side failure is identical on Windows.

## Issue 2: large responses crash the reader task, falsely report plugin exit, and leave the old process running

A different failure occurs when a complete JSON-RPC response line sent from Executa to the Python bridge exceeds the same boundary:

| Response frame size, excluding trailing LF | Observed result |
| --- | --- |
| 64,000 / 65,535 / 65,536 bytes | Returned intact; subsequent calls reuse the same plugin PID |
| 65,537 bytes | `tool_failed / executa process exited` |
| 100 KiB / 200 KiB / 1 MiB / 4 MiB | Same failure |

The 100 KiB, 200 KiB, and 1 MiB responses are clearly below the documented approximately 4 MiB channel budget, yet already fail in the local harness.

The actual bridge and dispatcher return:

```json
{
  "ok": false,
  "error": {
    "code": "tool_failed",
    "message": "executa process exited",
    "details": {}
  }
}

```

Further inspection showed that the error message does not accurately describe the process state:

- The plugin stdout reader task has terminated with `ValueError`.
- The harness has set the corresponding `_ExecutaProcess.closed` to `true`.
- **The original plugin process is still alive, with `proc.returncode` equal to `None`.**
- The bridge itself still responds to ping.
- The next small invoke succeeds, but starts a new plugin PID; the old plugin process remains alive and has not been reaped.

This should therefore not be attributed simply to the tool crashing. The harness’s response reader task fails, incorrectly reports a process exit, and replaces the process record on the next call.

### Identified cause

In `anna_app_runtime_local/executa.py`, the call to `asyncio.create_subprocess_exec()` does not set an explicit `limit` for the stdout/stderr readers. `_reader_loop()` then calls:

```python
line = await ep.proc.stdout.readline()

```

An oversized line terminates the reader task with an exception. Its `finally` block runs the following code for both normal EOF and reader exceptions:

```python
ep.closed = True
for fut in ep.pending.values():
    if not fut.done():
        fut.set_exception(_PluginError("executa process exited"))
ep.pending.clear()

```

This hides the actual line-length error and returns neither `result_too_large` nor information about the actual size. Based on this code, other calls waiting for responses from the same plugin may also fail together; we have not separately conducted a concurrency stress test.

On the next call, the harness sees `closed`, starts a new process, and overwrites the pool entry without first terminating and reaping the old process. We confirmed that the old process remained alive in the minimal plugin experiment.

The relevant `bridge.py` and `executa.py` files are each identical across the two runtime versions, which also explains why upgrading to CLI 0.1.53 still reproduces the failures.

## Controlled experiment: increasing the reader limit restores moderately sized responses

In an isolated diagnostic process, we temporarily set only the subprocess reader limit to `limit=8*1024*1024`, without modifying installed dependencies. Calling the same minimal plugin again produced the following results:

- Response frames of 65,537 bytes, 100 KiB, 200 KiB, 1 MiB, and 4 MiB were all read intact.
- The response reader task remained operational.
- Subsequent small calls continued reusing the same plugin PID.

This further confirms that the 64 KiB failure comes from the harness’s default reader limit.

**Increasing the buffer was a diagnostic experiment, not a complete fix.** A production implementation still needs to distinguish the underlying readable protocol frame size from each channel’s actual budget, account for envelope overhead, and return explicit errors when limits are exceeded. The request and response readers also need to be fixed separately.

## Impact on application development and responsiveness

PPT App transfers page HTML, preview data, and structured content through tool calls. To accommodate the current local environment, we use a 48 KiB inline budget and have implemented compression, upload references, and chunked reads for some text transfers.

These measures work around some failures, but add compression, upload, download URL retrieval, download, and repeated-call overhead. For page previews, page switching, and editing feedback, repeated transport round trips directly affect the user experience and increase implementation and debugging costs.

If approximately 4 MiB synchronous results were supported consistently by both the platform and the local harness, many moderately sized payloads could be transferred in a single invoke, potentially making the app substantially more responsive. Content that needs durable storage, or files that actually exceed the channel limit, would still be appropriate for APS or Host Upload.

The more serious issue today is the failure behavior: developers receive bridge-exit or tool-process-exit errors instead of a size-limit error they can handle. Applications cannot easily distinguish tool failures from transport failures, or reliably switch to reference-based transfer and continue after the failure.

## Expected behavior and questions for the platform

1. **Confirm a consistent synchronous result contract.** Is the documented approximately 4 MiB limit also the intended target for the local harness? Please clarify the serialization method, which envelopes count toward the limit, and the recommended usable payload budget.
2. **Document synchronous request argument limits separately.** Please distinguish synchronous requests, synchronous results, asynchronous requests, and asynchronous results, with exact byte limits and the corresponding errors.
3. **Fix both local transport directions.** Please review Node → bridge stdin, Executa → bridge stdout, and reverse-RPC frames passing through these readers, so that neither reader remains a hidden bottleneck.
4. **Fail explicitly on oversized data while keeping the runtime usable.** Return machine-readable errors with the actual size and allowed maximum. Do not terminate the entire bridge or misreport reader exceptions as plugin exits. Valid small requests should continue working after a failed call.
5. **Fix subprocess lifecycle handling.** When a reader task fails, handle pending calls appropriately, preserve the actual cause, and correctly reap or recover the associated process rather than leaving the old process running.
6. **Add boundary regressions and identify the fixed versions.** Cover 65,535 / 65,536 / 65,537 bytes, 100/200 KiB, values near and above 4 MiB, Chinese text and JSON escaping, Base64, and recovery after failure. Publish the runtime fix and update the CLI’s pinned runtime version.

Thank you for the earlier fix to the 32,000-character truncation issue. We hope this follow-up can bring the local harness and platform into alignment on transport capacity, size limits, and failure behavior, so developers can safely use synchronous results larger than 64 KiB according to the documentation, without discovering hidden limits through failures that disrupt local development.

---

<div class="post-metadata">

**Author:** ![hunter](https://yyz1.discourse-cdn.com/flex033/user_avatar/forum.anna.partners/hunter/32/8_2.png) [@hunter](https://forum.anna.partners/u/hunter)\
**Post date:** [September 24, 2026, 6:38am UTC](https://forum.anna.partners/t/bug-dev-harness-fails-at-64-kib-despite-the-documented-4-mib-invoke-result-limit-bridge-crashes-and-misleading-executa-exit-errors/338/2 "2026-09-24T06:38:01Z")

</div>

Hi @HappyLight! 👋

Thank you for another outstanding report — the isolated repro, the exact 65,536 / 65,537 byte boundary tests, and the analysis of both transport directions made this a joy to work with. 🙏 We confirmed everything you found, and you were exactly right about the root cause: both stdio readers in the local harness were still running on a default 64 KiB line limit, which contradicted the documented ~4 MiB synchronous result contract and failed in the worst possible way.

## ✅ Fixed — shipping this Friday

| Component | Fixed version |
| --- | --- |
| `anna-app-runtime-local` | **0.2.0a24** |
| `@anna-ai/cli` (pins the runtime) | **0.1.54** |

Both are scheduled to publish **this Friday**. Upgrading the CLI is all you need — no app-side changes required:

```bash
npm i -g @anna-ai/cli@latest # ≥ 0.1.54

```

## 🛠 What changed

**1. The 64 KiB hidden bottleneck is gone — in both directions.** 📏  
The stdio transport now allows frames up to **16 MiB** (the same ceiling the production Agent uses, sized so inline `host/uploadFile` reverse-RPC frames fit), on both the request path (harness → bridge) and the response path (Executa → harness).

**2. The local harness now enforces the same ~4 MiB sync result contract as the platform.**  
A synchronous `tools.invoke` result over ~4 MiB fails locally with the **identical** wire error production returns: `result_too_large` with `details: {size, max, retriable: false}`. Your 100 KiB / 200 KiB / 1 MiB payloads now pass through verbatim in `anna-app dev`, exactly as they do in production. Local testing and the platform finally agree. 🤝

**3. Oversized frames fail explicitly — and only the offending call.**

- An oversized **request** no longer terminates the bridge. It gets a structured JSON-RPC error (`frame_too_large`, with `frame_bytes` and `max_frame_bytes` in the error data), and subsequent calls keep working.
- An oversized **response** no longer crashes the reader task or misreports `executa process exited`. Only the affected call fails; the plugin process stays warm and is reused (same PID) for the next call.

**4. Process lifecycle fixed.**  
When a plugin genuinely exits, the harness now reaps the process properly and preserves the real failure cause — no more orphaned processes left behind on respawn.

**5. Boundary regressions added.** 🧪  
The exact cases from your report are now in our test suite: 65,535 / 65,536 / 65,537 bytes, 100 KiB, 1 MiB, just-over-4 MiB, over-16 MiB, and post-failure recovery on the same process.

## 📚 Docs

We’ve also updated the documentation to answer your contract questions explicitly:

- **Sync request arguments** now have a documented limit: they share the same ~4 MiB channel ceiling as results (async args remain 64 KB → `invalid_arg`).
- The **App UI Host API → Result size and integrity** section and the **Local Development** guide now document the local-harness parity, the 16 MiB frame cap, and the `frame_too_large` / `result_too_large` errors — including the old failure signatures so anyone hitting them on an older CLI finds the fix fast.

## 🚀 What this means for PPT App

You should be able to retire the 48 KiB inline budget and much of the compression / chunked-read machinery for moderately sized payloads — anything up to ~4 MiB now travels in a single synchronous invoke, both locally and in production. For anything larger, `host/uploadFile` and APS `files/*` remain the right tools, and you’ll now get a clean, machine-readable error instead of a broken bridge if you cross the line.

Thanks again for the rigor and care in these reports — they’ve directly made the platform better for every developer. Please give 0.1.54 a spin after Friday and let us know how it goes! 💛
