Idle probe: inline pane_id + watch list into bash script (drop positional args)
Root cause of "filter never suppresses": passing the target pane_id and watch names as positional args to `bash -c "..." _ <id> <names>` had them silently dropped by wsl.exe's arg-passing layer. Inside bash, $1 and $@ were empty — the script always looked for `TILETOPIA_PANE_ID=` (no value), found nothing, exited 1. Fix: format the script string in Rust with pane_id and watch names already substituted. No positional args to bash → nothing for wsl.exe to drop. Both inputs are safe to inline (u64 and a compile-time const list); validation needed if user-supplied watch names ever land here. Two unit tests guard against regressing to the positional-arg shape. Also dropped the diagnostic info!() spam added during debugging — back to debug! in the happy path, single concise probed= line on each cache miss. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6772b8db37
commit
9931a92c5f
1 changed files with 68 additions and 36 deletions
|
|
@ -92,6 +92,13 @@ impl ProbeCache {
|
||||||
// Slow path: re-probe. Drop the lock before shelling out so other
|
// Slow path: re-probe. Drop the lock before shelling out so other
|
||||||
// probes aren't blocked.
|
// probes aren't blocked.
|
||||||
let running = probe_pane(distro, pane_id, DEFAULT_WATCH_PROCESSES);
|
let running = probe_pane(distro, pane_id, DEFAULT_WATCH_PROCESSES);
|
||||||
|
tracing::debug!(
|
||||||
|
target: "tiletopia_lib::probe",
|
||||||
|
distro = %distro,
|
||||||
|
pane_id,
|
||||||
|
running,
|
||||||
|
"probed"
|
||||||
|
);
|
||||||
|
|
||||||
let mut guard = self.cache.lock();
|
let mut guard = self.cache.lock();
|
||||||
guard.insert(
|
guard.insert(
|
||||||
|
|
@ -111,33 +118,29 @@ impl Default for ProbeCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bash one-liner: for each watched process name, `pgrep -x` for it; for
|
/// Build a single-line bash script with the target pane_id and watch-list
|
||||||
/// each matching PID, check `/proc/<pid>/environ` for an exact
|
/// **interpolated directly** into the script text. The earlier design
|
||||||
/// `TILETOPIA_PANE_ID=<target>` entry (null-separated, so we `tr` it to
|
/// passed these as positional args (`bash -c "..." _ <id> <names...>`)
|
||||||
/// newlines and exact-line-match with `grep -xF`). Exit 0 = match, 1 = no
|
/// but `wsl.exe`'s arg-passing layer silently dropped everything after the
|
||||||
/// match, anything else = probe failure (treated as `false` upstream —
|
/// `-c` script string, so `$1`/`$@` were always empty inside bash.
|
||||||
/// see fail-safe note on `is_watch_process_running`).
|
/// Interpolating from Rust sidesteps the whole arg-passing path.
|
||||||
///
|
///
|
||||||
/// `bash` (not `sh`) is required for process substitution `< <(pgrep ...)`.
|
/// Both inputs are safe to inline: `pane_id` is a `u64` (no metachars) and
|
||||||
/// Both bash and pgrep are installed by default on every WSL distro
|
/// `watched` is a compile-time const list. If future code wires user-supplied
|
||||||
/// tiletopia targets; if a minimal distro is missing them the probe falls
|
/// process names through here, validate/escape them first.
|
||||||
/// to "not running" and the pane goes idle normally (better than the v1
|
///
|
||||||
/// fail-safe which kept suppressing forever).
|
/// Returns exit 0 if any watched process running in this specific pane has
|
||||||
const PROBE_SCRIPT: &str = r#"
|
/// the matching `TILETOPIA_PANE_ID` env marker; exit 1 otherwise.
|
||||||
target_id="$1"
|
fn build_probe_script(pane_id: u64, watched: &[&str]) -> String {
|
||||||
shift
|
let watch_list = watched
|
||||||
for name in "$@"; do
|
.iter()
|
||||||
while IFS= read -r pid; do
|
.map(|n| format!("\"{n}\""))
|
||||||
[ -z "$pid" ] && continue
|
.collect::<Vec<_>>()
|
||||||
if [ -r "/proc/$pid/environ" ]; then
|
.join(" ");
|
||||||
if tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep -qxF "TILETOPIA_PANE_ID=$target_id"; then
|
format!(
|
||||||
exit 0
|
r#"target_id={pane_id}; for name in {watch_list}; do for pid in $(pgrep -x "$name" 2>/dev/null); do [ -z "$pid" ] && continue; if [ -r "/proc/$pid/environ" ]; then if tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep -qxF "TILETOPIA_PANE_ID=$target_id"; then exit 0; fi; fi; done; done; exit 1"#
|
||||||
fi
|
)
|
||||||
fi
|
}
|
||||||
done < <(pgrep -x "$name" 2>/dev/null)
|
|
||||||
done
|
|
||||||
exit 1
|
|
||||||
"#;
|
|
||||||
|
|
||||||
fn probe_pane(distro: &str, pane_id: u64, watched: &[&str]) -> bool {
|
fn probe_pane(distro: &str, pane_id: u64, watched: &[&str]) -> bool {
|
||||||
if !cfg!(windows) {
|
if !cfg!(windows) {
|
||||||
|
|
@ -150,19 +153,15 @@ fn probe_pane(distro: &str, pane_id: u64, watched: &[&str]) -> bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compose args: bash -c <script> _ <pane_id> <watch>...
|
let script = build_probe_script(pane_id, watched);
|
||||||
// The `_` is `$0` for the script, then watch names are `$@`.
|
let args: Vec<String> = vec![
|
||||||
let mut args: Vec<String> = vec![
|
|
||||||
"-d".to_string(),
|
"-d".to_string(),
|
||||||
distro.to_string(),
|
distro.to_string(),
|
||||||
"--".to_string(),
|
"--".to_string(),
|
||||||
"bash".to_string(),
|
"bash".to_string(),
|
||||||
"-c".to_string(),
|
"-c".to_string(),
|
||||||
PROBE_SCRIPT.to_string(),
|
script,
|
||||||
"_".to_string(),
|
|
||||||
pane_id.to_string(),
|
|
||||||
];
|
];
|
||||||
args.extend(watched.iter().map(|s| s.to_string()));
|
|
||||||
|
|
||||||
let out = match crate::pty::quiet_command_pub("wsl.exe")
|
let out = match crate::pty::quiet_command_pub("wsl.exe")
|
||||||
.args(&args)
|
.args(&args)
|
||||||
|
|
@ -172,7 +171,7 @@ fn probe_pane(distro: &str, pane_id: u64, watched: &[&str]) -> bool {
|
||||||
{
|
{
|
||||||
Ok(o) => o,
|
Ok(o) => o,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::debug!(
|
tracing::info!(
|
||||||
"probe: wsl.exe spawn for distro={distro:?} pane={pane_id} failed: {e}"
|
"probe: wsl.exe spawn for distro={distro:?} pane={pane_id} failed: {e}"
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -180,8 +179,8 @@ fn probe_pane(distro: &str, pane_id: u64, watched: &[&str]) -> bool {
|
||||||
};
|
};
|
||||||
|
|
||||||
match out.status.code() {
|
match out.status.code() {
|
||||||
Some(0) => true, // watched process matching this pane found
|
Some(0) => true,
|
||||||
Some(1) => false, // no match
|
Some(1) => false,
|
||||||
Some(other) => {
|
Some(other) => {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"probe: distro={distro:?} pane={pane_id} bash exit={other} — treating as not-running"
|
"probe: distro={distro:?} pane={pane_id} bash exit={other} — treating as not-running"
|
||||||
|
|
@ -196,3 +195,36 @@ fn probe_pane(distro: &str, pane_id: u64, watched: &[&str]) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::build_probe_script;
|
||||||
|
|
||||||
|
/// Regression: positional args (`bash -c "..." _ <id> <names>`) were
|
||||||
|
/// silently dropped by wsl.exe's arg-passing layer, so `$1`/`$@` were
|
||||||
|
/// always empty inside bash. The script must inline pane_id + watch
|
||||||
|
/// names directly. Lock that in.
|
||||||
|
#[test]
|
||||||
|
fn build_probe_script_inlines_pane_id_and_watch_list() {
|
||||||
|
let s = build_probe_script(42, &["claude"]);
|
||||||
|
assert!(s.contains("target_id=42"), "pane_id not inlined: {s}");
|
||||||
|
assert!(s.contains("\"claude\""), "watch name not inlined: {s}");
|
||||||
|
// No "$1" or "$@" — those are the trap.
|
||||||
|
assert!(!s.contains("$1"), "script still references $1: {s}");
|
||||||
|
assert!(
|
||||||
|
!s.contains("\"$@\""),
|
||||||
|
"script still references $@: {s}"
|
||||||
|
);
|
||||||
|
// Sanity: matches the exact env marker shape pty.rs sets.
|
||||||
|
assert!(
|
||||||
|
s.contains("TILETOPIA_PANE_ID=$target_id"),
|
||||||
|
"marker lookup malformed: {s}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_probe_script_multiple_watch_names() {
|
||||||
|
let s = build_probe_script(7, &["claude", "vim", "less"]);
|
||||||
|
assert!(s.contains("\"claude\" \"vim\" \"less\""), "watch list bad: {s}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue