Skip to content

[Bug]: A rejected idle timeout containing a line-break character loses its typed diagnostic #5057

Description

@NIU-123370

Problem / 问题

When the managed TypeScript effect runtime rejects LOOPX_EFFECT_RUNTIME_IDLE_MS, it publishes exactly one JSON envelope on stderr carrying the typed code invalid_idle_timeout plus actionable guidance, and exits with status 2. loopx/control_plane/effect_runtime.py::_startup_diagnostic is the reader that turns that envelope back into a Python-side diagnostic, and its own docstring states the contract: "writes one JSON envelope to stderr".

The reader frames that stream with str.splitlines(), which treats U+0085 (NEL), U+2028 (LINE SEPARATOR) and U+2029 (PARAGRAPH SEPARATOR) as line breaks. The rejection message quotes the offending value back to the user, and the server builds it with JSON.stringify, which escapes every code point below U+0020 but leaves those three characters raw. So a rejected value that contains any of them tears the single documented record into fragments that no longer parse, and the caller falls back to the generic runtime_exited_before_ready (exit_code=2) — the same misleading diagnostic that #4106 was opened to remove, still reachable for these three inputs.

This is about which diagnostic reaches the operator, not about accept/reject: all of these values are already rejected, and no runtime-info file is written.

Measured against main at 3eac23563 / 实测

Driving the real managed boundary (effect_runtime.effect_runtime_result("runtime.ping", {}) with the environment variable set, Node 22.23.2):

Rejected value diagnostic_code observed Expected
U+0085 + 250 + U+0085 runtime_exited_before_ready invalid_idle_timeout
U+2028 + 250 + U+2028 runtime_exited_before_ready invalid_idle_timeout
U+2029 + 250 + U+2029 runtime_exited_before_ready invalid_idle_timeout
U+001C + 250 + U+001C invalid_idle_timeout ok
space + 250 + space invalid_idle_timeout ok

The split is exactly the set difference the mechanism predicts: U+001C is a control character, so JSON.stringify escapes it to \u001c and the record survives; the three above are not escaped and are honoured as newlines by splitlines().

Desired outcome / 期望行为

Frame the diagnostic stream on \n, the only separator the server writes, which is also what the response reader in the same module already does (raw.split(b"\n", 1)[0]). Any other stderr content, such as a Node.js stack trace, must keep reporting no typed diagnostic rather than being guessed at.

Do not change which values are accepted or rejected, the envelope schema, the exit status, or the guidance text.

Acceptance / 验收

  • A rejected value padded with each of U+0085, U+2028 and U+2029 surfaces invalid_idle_timeout through the real Python-to-TypeScript startup boundary and leaves no runtime-info file behind.
  • The framing contract is pinned at the reader itself, so a future change of separator cannot silently re-tear the record.
  • The already-correct U+001C and ASCII-whitespace paddings keep reporting invalid_idle_timeout, so the accepted set does not move in either direction.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions