Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions apps/decodex/src/orchestrator/entrypoints.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::io;

use state::{ConnectorBackoff, ConnectorBackoffInput};

use crate::runtime;
Expand Down Expand Up @@ -804,13 +806,26 @@ fn print_operator_status_snapshot(
snapshot: &OperatorStatusSnapshot,
json: bool,
) -> Result<()> {
if json {
println!("{}", serde_json::to_string_pretty(snapshot)?);
let output = if json {
format!("{}\n", serde_json::to_string_pretty(snapshot)?)
} else {
print!("{}", render_operator_status(snapshot));
}
render_operator_status(snapshot)
};
let stdout = io::stdout();
let mut stdout = stdout.lock();

Ok(())
write_cli_output(&mut stdout, &output)
}

fn write_cli_output<W>(writer: &mut W, output: &str) -> Result<()>
where
W: Write,
{
match writer.write_all(output.as_bytes()).and_then(|()| writer.flush()) {
Ok(()) => Ok(()),
Err(error) if error.kind() == ErrorKind::BrokenPipe => Ok(()),
Err(error) => Err(error.into()),
}
}

fn operator_connector_backoff_status(
Expand Down
41 changes: 41 additions & 0 deletions apps/decodex/src/orchestrator/tests/operator/status/text.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
mod operator_status_output_tests {
use std::io::{Error, ErrorKind, Result, Write};

struct BrokenPipeWriter;

struct FlushBrokenPipeWriter;

impl Write for BrokenPipeWriter {
fn write(&mut self, _buffer: &[u8]) -> Result<usize> {
Err(Error::from(ErrorKind::BrokenPipe))
}

fn flush(&mut self) -> Result<()> {
Ok(())
}
}

impl Write for FlushBrokenPipeWriter {
fn write(&mut self, buffer: &[u8]) -> Result<usize> {
Ok(buffer.len())
}

fn flush(&mut self) -> Result<()> {
Err(Error::from(ErrorKind::BrokenPipe))
}
}

#[test]
fn operator_status_output_accepts_closed_downstream_pipe() {
let mut writer = BrokenPipeWriter;

crate::orchestrator::write_cli_output(&mut writer, "partial status output\n")
.expect("broken stdout pipe should be accepted");

let mut writer = FlushBrokenPipeWriter;

crate::orchestrator::write_cli_output(&mut writer, "buffered status output\n")
.expect("broken stdout flush should be accepted");
}
}

use crate::execution_program::{
ExecutionLinearIssueMapping, ExecutionProgram, ExecutionProgramDependency,
ExecutionProgramNode, ExecutionProgramNodeStage, ExecutionQueueIntent,
Expand Down
4 changes: 3 additions & 1 deletion docs/reference/operator-control-plane.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,9 @@ JSON output identifies cache hits with `"status_source": "operator_snapshot_cach
the command falls back to a direct local runtime read and emits
`status_cached_snapshot_unavailable` in `warning_details`. `decodex status --live`
always bypasses the cached snapshot and rebuilds status with fresh Linear/GitHub
observers.
observers. If a downstream consumer such as `head` closes stdout before status output
finishes, the CLI treats that broken pipe as normal truncated output rather than an
operator status failure.

Operator JSON snapshots include `execution_programs[]` for Program Intake and
Execution Program readback. Each program row carries public intake kind/summary,
Expand Down
1 change: 1 addition & 0 deletions docs/spec/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ The minimum supported surface is:

- structured runtime logs with stable identifiers such as `project_id`, `issue_id`, `issue`, `run_id`, `attempt`, `branch`, and repository-relative `worktree_path`
- a local status command that renders the current service snapshot in both human-readable and JSON forms, including non-secret GitHub CLI authority diagnostics for the resolved command path, discovery tier, configured path when present, availability, and operator next action
- status command output must treat a downstream-closed stdout pipe as normal operator-side output truncation, not as a runtime, database, tracker, or GitHub failure
- an agent evidence command, `decodex diagnose`, that writes a compact derived handoff index, blocker snapshots, run capsules, and an append-only evidence event stream under `~/.codex/decodex/agent-evidence/<service-id>/`; the handoff index includes the same non-secret GitHub CLI authority readback so repair agents can diagnose missing or fallback-only `gh` authority

Structured logs remain diagnostic. They may help explain a live failure, but they are
Expand Down