mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
fix(cli): call it a program, and say that stdin does not reach it
`tty7 run -- /no/such/binary` answered "no such shell on this machine". The check in front of a spawn serves both the configured shell and a command handed to `run`, and its three sentences all said "shell" — so someone who typed `tty7 run -- ./build.sh` was told tty7 had misunderstood what they asked for. They say "program" now, which is true of both callers; the CLI already prefixes "spawning `…`" with the context. The other half is worse and is documented rather than changed: `echo hi | tty7 run -- cat` never returns. The command reads the pane's terminal, which nothing is typing into, so it waits for input that cannot arrive — and there is no error, because an idle terminal is not a failure. Output streams back; input does not go the other way. Measured, after a first attempt sat for ten minutes: a bounded re-run with `head -1` was still running at ten seconds with the pipe unread. For a CLI whose stated audience is agents, `cmd | tty7 run -- …` is a natural thing to try and an unbounded wait is the worst way to answer it. Forwarding stdin would be a new capability; saying so is not, and `sh -c 'cat < input.txt'` gets the input there today.
This commit is contained in:
@@ -67,7 +67,14 @@ pub enum Command {
|
||||
The environment comes from the server too, for the same reason, so \
|
||||
`FOO=bar tty7 run -- ...` does not reach the command. Set it where \
|
||||
the command can see it:\n\n \
|
||||
tty7 run -- sh -c 'FOO=bar cargo test'"
|
||||
tty7 run -- sh -c 'FOO=bar cargo test'\n\n\
|
||||
Nor is your stdin forwarded: the command reads from the pane's \
|
||||
terminal, which nothing is typing into. Output is streamed back, \
|
||||
input does not go the other way, so `echo hi | tty7 run -- cat` \
|
||||
waits for input that cannot arrive — with no error, because as far \
|
||||
as the pane is concerned a terminal is simply idle. Give the \
|
||||
command its input directly:\n\n \
|
||||
tty7 run -- sh -c 'cat < input.txt'"
|
||||
)]
|
||||
Run(RunArgs),
|
||||
|
||||
|
||||
@@ -122,10 +122,16 @@ struct SpawnConfig {
|
||||
shell: Option<ShellSpec>,
|
||||
}
|
||||
|
||||
/// Why a configured shell cannot be run, in one sentence, before anything
|
||||
/// tries. Without it a missing program arrives at the window wrapped four
|
||||
/// deep — "daemon refused Spawn: spawn failed: Unable to spawn … (ENOENT: No
|
||||
/// such file or directory)" — and the one fact that matters is buried in it.
|
||||
/// Why a program cannot be run, in one sentence, before anything tries.
|
||||
/// Without it a missing program arrives at the window wrapped four deep —
|
||||
/// "daemon refused Spawn: spawn failed: Unable to spawn … (ENOENT: No such
|
||||
/// file or directory)" — and the one fact that matters is buried in it.
|
||||
///
|
||||
/// "Program", not "shell": the same check stands in front of the configured
|
||||
/// shell and of a command handed to `tty7 run`, and calling the latter a shell
|
||||
/// tells someone who typed `tty7 run -- ./build.sh` that tty7 misunderstood
|
||||
/// what they asked for. The caller supplies the context — the CLI prefixes
|
||||
/// "spawning `…`" — so the sentence only has to carry the fact.
|
||||
///
|
||||
/// Only a program given as a path can be checked here; a bare name is resolved
|
||||
/// through PATH by the OS, and guessing at that would be worse than silence.
|
||||
@@ -135,16 +141,16 @@ fn shell_program_problem(program: &str) -> Option<String> {
|
||||
return None;
|
||||
}
|
||||
let Ok(meta) = std::fs::metadata(path) else {
|
||||
return Some(format!("no such shell on this machine: {program}"));
|
||||
return Some(format!("no such program on this machine: {program}"));
|
||||
};
|
||||
if meta.is_dir() {
|
||||
return Some(format!("the configured shell is a directory: {program}"));
|
||||
return Some(format!("that program is a directory: {program}"));
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
if meta.permissions().mode() & 0o111 == 0 {
|
||||
return Some(format!("the configured shell is not executable: {program}"));
|
||||
return Some(format!("that program is not executable: {program}"));
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -3191,7 +3197,7 @@ mod tests {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let missing = dir.path().join("not-a-shell");
|
||||
let problem = shell_program_problem(&missing.to_string_lossy()).expect("a problem");
|
||||
assert!(problem.contains("no such shell"), "{problem}");
|
||||
assert!(problem.contains("no such program"), "{problem}");
|
||||
assert!(problem.contains("not-a-shell"), "{problem}");
|
||||
// One sentence: none of the layers this used to arrive wrapped in.
|
||||
assert!(!problem.contains("ENOENT"), "{problem}");
|
||||
|
||||
Reference in New Issue
Block a user