Skip to content

CI run for PR #10 (fork PRs don't trigger the Rust matrix) - #18

Closed
ualtinok wants to merge 5 commits into
masterfrom
ci/pr10-stderr-tail
Closed

ualtinok wants to merge 5 commits into
masterfrom
ci/pr10-stderr-tail

Conversation

@ualtinok

@ualtinok ualtinok commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Internal twin of #10 (iceteaSA's stderr-tail feature) to run the full CI matrix — fork PRs only get Socket/cubic here, so the Rust legs never ran. Closes after the verdict; the merge happens on #10 itself.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.


Summary by cubic

Adds a bounded per-module stderr tail retained in subc-core and exposes it via a new control op and ck verb, so crash causes remain readable even if the daemon log rotated. Previously stderr only went to the daemon’s inherited fd; now each module’s stderr is piped into a ring buffer and still forwarded verbatim to the daemon log.

  • New control op: supervisor.stderr_tail. Request takes module_id and optional max_lines/max_bytes; response returns capture state (captured/incomplete/not_captured), entries (lines with truncated flag and in-band process_start markers), and dropped_lines.
  • Supervisor behavior: pipe child stderr, run a pump that retains a bounded tail (per-line truncation; ring survives restarts), drains the old pump on restart before inserting a boundary, and records reasons for incomplete/uncaptured tails; stdout remains inherited.
  • CLI: ck module stderr <id> [-n <count>] prints the retained tail (with dropped count header, truncation markers, and not-captured/incomplete messages). --json returns raw wire.
  • Review focus: core logic in subc-core/src/stderr_tail.rs; spawn/monitor changes in subc-core/src/supervise.rs; RPC handler in subc-core/src/control.rs; wire types and goldens in subc-control. No migrations required; server.describe now advertises supervisor.stderr_tail.

Written for commit 9886d1e. Summary will update on new commits.

Review in cubic

…mon tests

Snapshot commit so nothing is lost. Authored by the main agent in violation of
the delegate-code-writing rule; handed to an implementer for triage of the red
gate and to an independent reviewer for acceptance.
…ation once

Count max_lines as stderr lines while preserving meaningful restart boundaries.\n\nAdvertise supervisor.stderr_tail in server.describe and its golden mirrors.\n\nKeep truncated text as a prefix, cover real ring-to-wire conversion, and format the workspace.
Enforce max_line_bytes <= max_bytes at construction time, clamping oversized line caps so stderr diagnostics degrade instead of blocking supervisor startup.
…inventing newlines

Drain each stderr pump before a replacement boundary, preserving valid partial tails as incomplete captures, forwarding unterminated fragments byte-for-byte, and polling stderr assertions on the conditions they verify.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 15 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="crates/subc-core/src/stderr_tail.rs">

<violation number="1" location="crates/subc-core/src/stderr_tail.rs:370">
P2: When `source.read` fails after a partial line, `pump_stderr_into` drops the already-read fragment before returning. Flush `pending` through `emit_line` before marking the capture incomplete.</violation>
</file>

<file name="crates/subc-core/src/supervise.rs">

<violation number="1" location="crates/subc-core/src/supervise.rs:2722">
P1: When the daemon's stderr is a slow or blocked pipe, this Tokio task performs blocking `write_all`, so a noisy child can block a runtime worker and eventually fill its stderr pipe. Multiple such modules can stall health checks, command handling, and restarts; forward through nonblocking/async I/O or isolate the blocking write with bounded backpressure.</violation>
</file>

You're on the cubic free plan with 12 free PR reviews remaining this month. Upgrade for unlimited reviews.

Re-trigger cubic

Comment thread crates/subc-core/tests/supervision.rs Outdated
ring.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.push_process_start();
Some(tokio::spawn(pump_stderr(stderr, Arc::clone(ring))))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When the daemon's stderr is a slow or blocked pipe, this Tokio task performs blocking write_all, so a noisy child can block a runtime worker and eventually fill its stderr pipe. Multiple such modules can stall health checks, command handling, and restarts; forward through nonblocking/async I/O or isolate the blocking write with bounded backpressure.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/subc-core/src/supervise.rs, line 2722:

<comment>When the daemon's stderr is a slow or blocked pipe, this Tokio task performs blocking `write_all`, so a noisy child can block a runtime worker and eventually fill its stderr pipe. Multiple such modules can stall health checks, command handling, and restarts; forward through nonblocking/async I/O or isolate the blocking write with bounded backpressure.</comment>

<file context>
@@ -2580,35 +2672,74 @@ fn spawn_child(
+            ring.lock()
+                .unwrap_or_else(|poisoned| poisoned.into_inner())
+                .push_process_start();
+            Some(tokio::spawn(pump_stderr(stderr, Arc::clone(ring))))
+        }
+        None => {
</file context>

S: LineSink,
{
{
let mut guard = lock_ring(&ring);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When source.read fails after a partial line, pump_stderr_into drops the already-read fragment before returning. Flush pending through emit_line before marking the capture incomplete.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/subc-core/src/stderr_tail.rs, line 370:

<comment>When `source.read` fails after a partial line, `pump_stderr_into` drops the already-read fragment before returning. Flush `pending` through `emit_line` before marking the capture incomplete.</comment>

<file context>
@@ -0,0 +1,923 @@
+    S: LineSink,
+{
+    {
+        let mut guard = lock_ring(&ring);
+        guard.mark_captured();
+    }
</file context>

@ualtinok

Copy link
Copy Markdown
Contributor Author

Served its purpose: full matrix on #10's head — ubuntu green, windows red (deterministic /bin/sh spawn, see review on #10). Future fork PRs get the same twin treatment until fork CI is enabled.

@ualtinok ualtinok closed this Aug 14, 2026
…erage

The internal CI twin (origin/ci/pr10-stderr-tail) of this PR ran the full
platform matrix: ubuntu green, windows red. Five stderr-tail tests spawned
PathBuf::from("/bin/sh") directly, which resolves to nothing on Windows.
Cargo's fail-fast stopped after the first visible failure, hiding the other
four.

Extends the existing fake-aft-stub rather than #[cfg(unix)]-gating the
tests, so the stderr tail stays verified on the platform whose CRT stdio
behaviour differs most from Linux.

New stub knobs (same const/env-var convention as the ~30 existing ones):
- FAKE_AFT_EXIT_CODE: presence-triggered short-circuit that writes the
  configured stderr line and exits with this code BEFORE ever touching
  the connection file or dialling subc -- verified the normal connect/
  HELLO/register path never runs for these five tests, so a connect
  failure can't pollute the very stderr ring under test.
- FAKE_AFT_STDERR_LINE: text to write first, with a {pid} token
  substituted with this process's real pid (for the restart-boundary
  test, which needs successive generations to be distinguishable).
- FAKE_AFT_ORPHAN_WRITER_DELAY_MS / FAKE_AFT_ORPHAN_WRITER_LINE: makes
  the stub re-exec itself (env::current_exe) with a marker env var and
  Stdio::inherit() on stderr, then exit without waiting on the child.
  Dropping a std::process::Child does not kill it, so the orphan keeps
  this process's stderr write-end duplicate open past the parent's own
  exit -- reproducing the wedged-pump property the fifth test asserts
  on (an old generation's stderr must not cross the restart boundary),
  portably, with no shell, no 1933377, no & backgrounding, no sleep binary.

The control.rs unit test (unlike tests/*.rs integration tests) doesn't get
CARGO_BIN_EXE_* from cargo at all; added fake_aft_stub_path() to locate the
sibling binary via current_exe() instead.

Golden fixtures for supervisor.stderr_tail are untouched (verified via
git diff against 9ab5a82). Full subc-core + subc-control suites, clippy,
and fmt all pass; the four stderr-tail supervision tests pass 10/10 in a
tight loop, including the timing-sensitive wedged-pump case.
@ualtinok

Copy link
Copy Markdown
Contributor Author

Twin served its purpose: full matrix green at 955c04a, #10 merged.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants