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
|
||||
// probes aren't blocked.
|
||||
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();
|
||||
guard.insert(
|
||||
|
|
@ -111,33 +118,29 @@ impl Default for ProbeCache {
|
|||
}
|
||||
}
|
||||
|
||||
/// Bash one-liner: for each watched process name, `pgrep -x` for it; for
|
||||
/// each matching PID, check `/proc/<pid>/environ` for an exact
|
||||
/// `TILETOPIA_PANE_ID=<target>` entry (null-separated, so we `tr` it to
|
||||
/// newlines and exact-line-match with `grep -xF`). Exit 0 = match, 1 = no
|
||||
/// match, anything else = probe failure (treated as `false` upstream —
|
||||
/// see fail-safe note on `is_watch_process_running`).
|
||||
/// Build a single-line bash script with the target pane_id and watch-list
|
||||
/// **interpolated directly** into the script text. The earlier design
|
||||
/// passed these as positional args (`bash -c "..." _ <id> <names...>`)
|
||||
/// but `wsl.exe`'s arg-passing layer silently dropped everything after the
|
||||
/// `-c` script string, so `$1`/`$@` were always empty inside bash.
|
||||
/// Interpolating from Rust sidesteps the whole arg-passing path.
|
||||
///
|
||||
/// `bash` (not `sh`) is required for process substitution `< <(pgrep ...)`.
|
||||
/// Both bash and pgrep are installed by default on every WSL distro
|
||||
/// tiletopia targets; if a minimal distro is missing them the probe falls
|
||||
/// to "not running" and the pane goes idle normally (better than the v1
|
||||
/// fail-safe which kept suppressing forever).
|
||||
const PROBE_SCRIPT: &str = r#"
|
||||
target_id="$1"
|
||||
shift
|
||||
for name in "$@"; do
|
||||
while IFS= read -r pid; 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 < <(pgrep -x "$name" 2>/dev/null)
|
||||
done
|
||||
exit 1
|
||||
"#;
|
||||
/// Both inputs are safe to inline: `pane_id` is a `u64` (no metachars) and
|
||||
/// `watched` is a compile-time const list. If future code wires user-supplied
|
||||
/// process names through here, validate/escape them first.
|
||||
///
|
||||
/// Returns exit 0 if any watched process running in this specific pane has
|
||||
/// the matching `TILETOPIA_PANE_ID` env marker; exit 1 otherwise.
|
||||
fn build_probe_script(pane_id: u64, watched: &[&str]) -> String {
|
||||
let watch_list = watched
|
||||
.iter()
|
||||
.map(|n| format!("\"{n}\""))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
format!(
|
||||
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"#
|
||||
)
|
||||
}
|
||||
|
||||
fn probe_pane(distro: &str, pane_id: u64, watched: &[&str]) -> bool {
|
||||
if !cfg!(windows) {
|
||||
|
|
@ -150,19 +153,15 @@ fn probe_pane(distro: &str, pane_id: u64, watched: &[&str]) -> bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Compose args: bash -c <script> _ <pane_id> <watch>...
|
||||
// The `_` is `$0` for the script, then watch names are `$@`.
|
||||
let mut args: Vec<String> = vec![
|
||||
let script = build_probe_script(pane_id, watched);
|
||||
let args: Vec<String> = vec![
|
||||
"-d".to_string(),
|
||||
distro.to_string(),
|
||||
"--".to_string(),
|
||||
"bash".to_string(),
|
||||
"-c".to_string(),
|
||||
PROBE_SCRIPT.to_string(),
|
||||
"_".to_string(),
|
||||
pane_id.to_string(),
|
||||
script,
|
||||
];
|
||||
args.extend(watched.iter().map(|s| s.to_string()));
|
||||
|
||||
let out = match crate::pty::quiet_command_pub("wsl.exe")
|
||||
.args(&args)
|
||||
|
|
@ -172,7 +171,7 @@ fn probe_pane(distro: &str, pane_id: u64, watched: &[&str]) -> bool {
|
|||
{
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
tracing::info!(
|
||||
"probe: wsl.exe spawn for distro={distro:?} pane={pane_id} failed: {e}"
|
||||
);
|
||||
return false;
|
||||
|
|
@ -180,8 +179,8 @@ fn probe_pane(distro: &str, pane_id: u64, watched: &[&str]) -> bool {
|
|||
};
|
||||
|
||||
match out.status.code() {
|
||||
Some(0) => true, // watched process matching this pane found
|
||||
Some(1) => false, // no match
|
||||
Some(0) => true,
|
||||
Some(1) => false,
|
||||
Some(other) => {
|
||||
tracing::debug!(
|
||||
"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