Skip to content

/track review: repetition safety gate, process cleanup, bounded presentation - #421

Merged
raiseCatError merged 5 commits into
devfrom
fix/v021-stabilize
Oct 18, 2026
Merged

raiseCatError merged 5 commits into
devfrom
fix/v021-stabilize

Conversation

@raiseCatError

Copy link
Copy Markdown
Owner

Review of #416 and #420 together. Stacked on #420; /track stays in v0.21, window-owned.

Findings and fixes

Safety gate (commands /track and /watch started at once and repeated without asking)

Command Before Now
echo x > ~/.zshrc 2>/dev/null allowed (any >/dev/null excused every redirect; paste review said read-only) refused; each redirect judged on its own target
ls >| out asked (split as a pipe) refused
env rm -rf …, env -i …, FOO=1 df, LD_PRELOAD=… df allowed / asked asks first (runs or changes another command)
npm publish, npm login, npm audit fix, npm x …, pnpm dlx …, bun x … allowed asks first
ssh host rm -rf x, curl -X POST, curl -d, curl -o file, plain wget, gopher:// allowed (unless a dotted remote host was spotted) asks first
scp, rsync allowed refused (copies files; rsync -n asks)
git -c core.fsmonitor=… status, GIT_EXTERNAL_DIFF=… git diff, --ext-diff allowed asks first
git log --output=f, sort -o, find -fprint, tree -o, yq -i, jest -u, --fix, --write, --watch allowed refused
shutdown, reboot, halt, poweroff, systemctl reboot asked refused
npm test 2>&1 refused as a background job allowed
ping 8.8.8.8, ping nas local (2 s minimum) remote (asks, 15 s minimum)

Read-only probes still start at once: ping/dig/nslookup/host/traceroute, nc -z, a curl GET/HEAD to stdout or -o /dev/null, wget -O -/--spider, npm view/outdated/audit. &> stays refused: dash, /bin/sh on Debian/Ubuntu, reads it as a background job.

Process lifecycle

  • When a sample's shell exited, the close handler cancelled the KILL fallback even when other members of its process group were still alive. A TERM-ignoring child that closed its output survived /track stop (reproduced with a pid probe). On close, anything left in the group now gets TERM, then KILL after the grace period. That timer keeps a closing window alive for at most 1.5 s.
  • fix(v0.21): harden /track refresh and finalization lifecycle #420's refresh-start event now fires after the child's handlers are attached. A throwing listener can no longer leave a child unwired.
  • A tracker whose directory disappeared said spawn /bin/sh ENOENT; it now names the directory and keeps retrying.

Presentation

  • More than two active trackers share one summary row above the composer (eight trackers took eight rows).
  • State marks respect Safe glyphs. The unknown-command confirmation says "Track …?".
  • Live rows split and sanitized the whole 200 kB result every frame (1–3 ms per frame with eight large trackers). This is now computed once per result (~0.5 ms per frame).

#420 itself: reviewed. Re-entrant rendering: render() is synchronous and only reads the scheduler, and every emit happens after scheduler state is consistent. Stale references: a deleted trackBlocks entry is only read through get(id) with a stopped fallback. Duplicate finalization: stop() emits once, record() ignores stopped trackers, and the window's exit unsubscribes before dispose(). No defects found beyond the event ordering above.

Tests

  • tests/trackSafety.test.ts (new): redirects, wrappers, git options, file-writing options, network probes vs senders, remote detection.
  • tests/track.test.ts: leftover processes after stop and after a finished sample (fails without the fix), the missing-directory message, Safe-glyph marks, the summary row.
  • tests/trackLive.test.ts (real PTY): four trackers at once (slow with refreshing…, failing, missing command, large output), a resize, /track stop all ending every process; Bash and Fish trackers seeing a PATH change made after startup.

Docs: docs/development/track-live-command-refresh.md, physical QA 5.7–5.11, CHANGELOG.

Refs #393, #410, #416, #420

…on its own target

Review of #416/#420 found commands that /track (and /watch) started at once and
repeated every interval without asking:

- `echo x > ~/.zshrc 2>/dev/null`: the shared classifier excused every redirect in
  a command once `>/dev/null` appeared anywhere; `>|` was split as a pipe. Paste
  review labelled the same text read-only.
- `env rm -rf …`: `env` is read-only on its own but runs its operand.
- Anything classified network: `npm publish`, `npm login`, `npm audit fix`,
  `npm x`/`pnpm dlx`/`bun x`, `ssh host rm …`, `scp`, `rsync`, `curl -X POST`,
  `curl -o file`, plain `wget`.
- `git -c core.fsmonitor=… status`, `GIT_EXTERNAL_DIFF=… git diff`, `--ext-diff`
  (run arbitrary programs) and `git log --output=…` (writes files).

pasteGuard now judges each redirection's target (descriptor duplication and the
null/standard-stream devices are not files) and treats `>|` as a redirection.
repeatSafety.ts adds the repetition gate on top of the classifier: network
commands start at once only as read-only probes; env prefixes, wrappers and git
program options ask first; file-writing options, --fix/--write/snapshot updates,
--watch modes, scp/rsync and power control are refused. `2>&1` is no longer
mistaken for a background job. Network targets are remote unless they are this
computer.

Refs #393, #416, #420
… after wiring

- When a sample's shell exits, the close handler cancelled the KILL fallback even
  if other members of its process group were still running. A child that ignored
  TERM and closed its output survived `/track stop` (verified with a pid probe;
  the new test fails without the fix), and a stray process left by a finished
  sample piled up on every refresh. On close, anything still in the group now gets
  TERM, then KILL after the grace period; that timer keeps a closing window alive
  for at most the grace period so nothing is left behind.
- #420's refresh-start event now fires after the sample's handlers are attached,
  so a listener that throws cannot leave a child without them.
- A tracker whose directory disappeared said `spawn /bin/sh ENOENT`; it now names
  the directory and keeps trying at its interval.

Refs #393, #416, #420
…esult rows

- Above the composer each active tracker had its own row, repeating its block's
  status; eight trackers took eight rows. More than two now share one summary row
  (`4 trackers · 1 failing · /track opens the manager`).
- The state marks (◉ ■ ‖ ◌) ignored Safe glyphs; they now use ASCII there, in the
  block and the manager.
- Live rows split and sanitized the whole 200 kB result on every frame (1–3 ms per
  frame with eight large trackers); the first rows and line count are now computed
  once per result (~0.5 ms).
- The confirmation for an unknown /track command says "Track …?", not "Watch …?".
- Live PTY tests: four trackers at once (slow, failing, missing command, large
  output) with `refreshing…`, a resize and `/track stop all` ending every process;
  Bash and Fish trackers finding a PATH change made after startup.

Refs #393, #416, #420
…s samples

dash reads `cmd &>/dev/null` as `cmd &` followed by an empty redirection, so the
command would run detached. Descriptor duplication (`2>&1`, `>&2`) stays allowed.

Refs #393, #420
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant