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
1 change: 1 addition & 0 deletions docs/next/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Windows clients now preserve layout-generated text for Shift-only keys, so characters such as `/` on German keyboards reach shell panes and pasted input. (#3045)
- Windows panes now keep bare `cursor-agent` launches detected after Cursor hands off to its bundled Node process. (#3032)
- Oversized Kitty images no longer prevent smaller images shown later in the same pane from rendering. (#3033)
- `herdr agent explain --file` now reports fixture read failures as structured JSON instead of exposing Rust I/O debug output. (#3022)
- Claude Code panes now use visible turn, background shell, and background agent activity as working-state fallbacks when OSC titles are unavailable or disabled. (#1630, #2241)
- Claude Code panes now remain working while MCP tasks continue in the background after a turn ends. (#3090)
- Tab bar status commands now remove ESC-prefixed terminal control sequences instead of displaying their sequence bodies as text. (#3001)
Expand Down
20 changes: 17 additions & 3 deletions src/cli/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use std::time::{Duration, Instant};

use crate::api::schema::{
AgentPromptParams, AgentPromptWaitOptions, AgentReadParams, AgentRenameParams,
AgentSendKeysParams, AgentStartParams, AgentTarget, AgentWaitParams, EmptyParams, Method,
PaneProcessInfoParams, PaneTarget, ReadFormat, ReadSource, Request,
AgentSendKeysParams, AgentStartParams, AgentTarget, AgentWaitParams, EmptyParams, ErrorBody,
ErrorResponse, Method, PaneProcessInfoParams, PaneTarget, ReadFormat, ReadSource, Request,
};

const AGENT_START_POLL_INTERVAL: Duration = Duration::from_millis(100);
Expand Down Expand Up @@ -118,7 +118,21 @@ fn agent_explain(args: &[String]) -> std::io::Result<i32> {
eprintln!("herdr agent explain --file requires --agent LABEL");
return Ok(2);
};
let content = std::fs::read_to_string(path)?;
let content = match std::fs::read_to_string(&path) {
Ok(content) => content,
Err(err) => {
let response = ErrorResponse {
id: "cli:agent:explain".into(),
error: ErrorBody {
code: "agent_explain_file_read_failed".into(),
message: format!("failed to read agent explain file {path}: {err}"),
},
};
let response = serde_json::to_string(&response).map_err(std::io::Error::other)?;
eprintln!("{response}");
return Ok(1);
}
};
crate::detect::manifest::explain_to_json_value(&crate::detect::manifest::explain_for_label(
&agent_label,
&content,
Expand Down
29 changes: 29 additions & 0 deletions tests/cli/agents.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
use super::harness::*;

#[test]
fn agent_explain_missing_file_reports_json_error() {
let base = unique_test_dir();
let missing = base.join("missing-screen.txt");
let output = run_named_cli(
&base.join("config"),
&base.join("runtime"),
&[
"agent",
"explain",
"--file",
missing.to_str().unwrap(),
"--agent",
"claude",
"--json",
],
);

assert_eq!(output.status.code(), Some(1));
assert!(output.stdout.is_empty());
let error: serde_json::Value = serde_json::from_slice(&output.stderr).unwrap();
assert_eq!(error["id"], "cli:agent:explain");
assert_eq!(error["error"]["code"], "agent_explain_file_read_failed");
assert!(error["error"]["message"]
.as_str()
.unwrap()
.contains(missing.to_str().unwrap()));
}

fn write_delayed_shell_and_fake_pi(
base: &Path,
shell_delay_seconds: &str,
Expand Down
Loading