FE-1221: Add structured correlated logging across the Petrinaut optimization pipeline#9059
Conversation
…ization pipeline
One optimization is now traceable end to end: NodeAPI's x-hash-request-id
is forwarded upstream, the optimizer's run_id comes back as
X-Optimization-Run-ID and is logged on both sides, and the CLI receives
the run id at spawn as PETRINAUT_CORRELATION_ID.
- petrinaut-optimizer-client: openPetrinautOptimizationStream accepts a
requestId (sent as x-hash-request-id) and returns { events,
optimizationRunId } read from the X-Optimization-Run-ID header.
- hash-api: request-scoped child logger (Apollo pattern); logs request
acceptance, validation failures (issue counts and schema paths only),
capacity rejections, stream opening, and a terminal outcome with
durationMs (completed / timeout kind / upstream-error /
client-disconnected — disconnects no longer return silently).
- petrinaut-opt: stdlib JSON-lines logging to stdout (logging_config.py,
HASH_PETRINAUT_OPT_LOG_LEVEL, idempotent setup); inbound
x-hash-request-id and traceparent headers threaded into every study
lifecycle log (admission, capacity rejection, initialization,
study start/completion, per-trial outcomes, wall-clock ceiling,
disconnect, worker join timeout, CLI termination path, slot release).
CLI stderr is no longer discarded: each line is forwarded to a pn_cli
logger, truncated with a bounded pending buffer.
- petrinaut-cli: minimal JSON stderr diagnostics after the unchanged
ready handshake (bootstrap_completed, per-request id/method/durationMs/
outcome, stdin_eof), correlation id injected via cli.ts.
Logs never include manifests, user-authored code, or raw request bodies;
docs updated (README logging section, CLI stderr contract, threat model).
- Cap forwarded CLI stderr at 1000 lines/run (drain the rest) so a chatty or hostile CLI cannot flood the log pipeline; seed the drain thread with bytes read past the bootstrap handshake so an early CLI diagnostic is forwarded rather than dropped. - Truncate the per-trial pruned-error field and stop interpolating the raw CLI error into the log message; correct the docstring. - Log an error type and coarse category instead of the raw initialization error string, which can embed a user identifier or expression error; the full message still reaches the requester in the 500 detail. - Cap validation issue-path segments before they reach the 400 response or the log, since manifest keys are user-controlled and unbounded. - Capture the optimizer run id from a failed response so a pre-stream upstream failure still correlates end to end. - Charset-validate the traceparent trace id instead of int(..., 16), which accepted 0x-prefixed, underscored, and signed values. - Document the aggregate stderr cap and header validation in the threat model.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview NodeAPI now uses a request-scoped child logger and logs acceptance, validation failures (counts and bounded paths only), capacity rejections, stream open, and terminal outcomes including client disconnect. It forwards
Petrinaut CLI emits JSON stderr diagnostics after the unchanged ready line ( Docs and tests assert redaction (no manifests, user code, or unbounded user-controlled strings in logs). Reviewed by Cursor Bugbot for commit 2f81742. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2f81742. Configure here.
| optimizationRunId, | ||
| outcome: "completed", | ||
| userId, | ||
| }); |
There was a problem hiding this comment.
Terminal log marks failures completed
Medium Severity
After forwardOptimizationEvents returns, the handler always logs Petrinaut optimization finished with outcome: "completed". That path runs even when the last forwarded NDJSON event was type: "error" from upstream SSE, so failed studies are recorded as completed in structured logs.
Reviewed by Cursor Bugbot for commit 2f81742. Configure here.


Closes FE-1221 (part of FE-1217).
Stacked on #9058 (base
cf/fe-1220-...).What
Structured, correlated logs across NodeAPI, the Python optimizer, and the Petrinaut CLI, with one correlation chain traceable end to end:
x-hash-request-id(NodeAPI) →run_id/X-Optimization-Run-ID(optimizer) →PETRINAUT_CORRELATION_ID(CLI spawn env) →correlationIdin CLI stderr diagnostics.NodeAPI (
apps/hash-api,@local/petrinaut-optimizer-client)openPetrinautOptimizationStreamgains arequestIdoption (sent asx-hash-request-id) and returns{ events, optimizationRunId }(from theX-Optimization-Run-IDresponse header; also captured from failed responses).logger.child({ requestId })and logs request acceptance, validation failures (issue counts + bounded schema paths only), capacity rejections, stream-opened (with the optimizer run id), and a terminal outcome withdurationMs— including the previously-silent client-disconnect case.Optimizer service (
apps/petrinaut-opt)src/logging_config.py: JSON-lines formatter to stdout (service, correlation fields,HASH_PETRINAUT_OPT_LOG_LEVEL), idempotent config, charset-validatedtraceparentparsing.x-hash-request-id/traceparent; lifecycle events (admitted, capacity rejection, init started/failed, study started/completed/failed, per-trial outcomes, wall-clock ceiling, client disconnect, worker-join timeout, CLI termination path, slot release) log withrun_id/request_id/trace_id.pn_clilogger, bounded per-line (2000 chars), in pending memory (16 KiB), and per run (1000 lines, then drained silently).Petrinaut CLI
src/runtime/diagnostics.ts: JSON-per-line stderr logger. The ready-handshake line remains the exact, unchanged first stderr line; subsequent lines arebootstrap_completed, onerequestline per handled request (id/method/durationMs/outcome— never params or error text), andstdin_eof.Redaction
Logs never include the manifest, user-authored code, or raw request bodies. Error strings that can embed user identifiers (per-trial CLI errors, init-failure messages) are truncated and/or replaced with a stable classification in logs; the full message still reaches the requester in the HTTP response. Reviewed adversarially; the confirmed findings (unbounded aggregate stderr forwarding, raw error strings in logs, unbounded validation-path log field, dropped early CLI diagnostic, uncorrelated pre-stream failure, permissive traceparent parsing) are all fixed in this branch.
Tests
apps/petrinaut-opt: 89 pytestapps/hash-apipetrinaut-optimizer: 19 vitest@local/petrinaut-optimizer-client: 15 vitestlibs/@hashintel/petrinaut-cli: 36 vitest🤖 Generated with Claude Code