WSL port 3/7: make the timer ring, and stop it killing strangers

Merged
JA
3mo ago
+166-23

Stacked on #8. Two separate bugs, neither of which the suite could see until it
could run on Linux at all.

1. t stopped whatever it found, not whatever was a timer

pgrep -f "$TAG" matches the tag anywhere in a command line — so it also
matched every process that merely mentions it: a shell started as
sh -c '... DRILL_TIMER_TAG=drill-timer ...', an editor opened on a file with
that name, a grep for it. "Stop the running timer" is the first thing t does,
so t stopped those instead.

Reproduced from a script file, so no ancestor held the tag:

=== who does pgrep -f see, from inside a shell whose argv holds the tag? ===
my pid: 2086961
pgrep -f sees:
    2086961 bash -c  . '/home/jainal09/drill/drill.sh' ... DRILL_TIMER_TAG='drill-ti

=== now the real thing: t ===
Terminated
outer saw exit=143

The shell was found and killed by its own call to t. That is also exactly
what the test harness does, which is why 30 of 36 timer cases failed here
before any of this
— the suite was killing itself, on unmodified main.

The fix: ps -o comm= is the process's executable, not its arguments, so it
cannot be fooled by a command line that quotes the tag.

pid 2098008  comm=zsh          <- the shell that mentions the tag
pid 2098013  comm=python3      <- the actual timer

POSIX, and identical on macOS. The suite gets its own copy of that filter
rather than calling _drill_timer_pids, so it is not checking drill.sh against
itself.

2. The sound and the notification did nothing — and so did the bell

run() returned True when a process merely launched. Measured on Ubuntu
22.04 under WSLg, both of these are installed and both exit non-zero:

commandstatusexit
pw-play …/complete.ogainstalled1 — no node available
notify-send drill testinstalled1 — org.freedesktop.Notifications was not provided by any .service files

Counted as success, they consumed the chain and left the \a fallback
unreachable: no sound, no notification, and no bell — the one outcome a timer
must never have.

So: believe the exit code, add a timeout=10 so a notify-send waiting on a
dead bus cannot hang the process for the session, and give each chain the legs it
needs past macOS.

sound chain:
   afplay       which=False run=False     <- mac leg, correctly skipped
   paplay       which=True  run=True
   pw-play      which=True  run=False     <- the case that used to count as success
   play()  -> True in 2.0s
notify chain:
   notify-send  run = False
   notify() -> True in 0.4s               <- Windows toast

The mac leg stays first and unchanged in both chains, and afplay and
osascript exit 0 on a Mac, so nothing after them is ever reached there.

One judgement call worth flagging: I first put alarm-clock-elapsed.oga at the
head of the sound list, then measured it — it is an 8-second looping alarm,
and this plays three times. complete.oga stays first, as it always was.

Suite

556/556, exit 0 — the first time the gate has passed on WSL.

timertotal
unmodified main6/36526
after checkpoint 26/36526
this branch36/36556

Not verified here

The toast returns exit 0, which means Windows accepted it. Whether it visibly
appears also depends on Focus Assist and notification settings — worth a glance
on your side.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WveBqAyhoM49p9gWfwBDBx

Summary by CodeRabbit

  • Bug Fixes

    • Improved timer reliability by detecting failed commands and handling timeouts correctly.
    • Enhanced sound and desktop notification support across platforms, with additional fallback options.
    • Prevented timer controls from affecting unrelated processes.
    • Improved timer listing behavior when process discovery is unavailable.
  • Tests

    • Expanded timer coverage to verify process handling, restarts, notifications, sound playback, and all 36 timer scenarios.
  • Documentation

    • Updated checkpoint documentation to reflect completed validation and reliability improvements.

Greptile Summary

The PR improves cross-platform timer process discovery and makes sound and notification fallbacks depend on command exit status.

  • Adds Linux and Windows sound and notification fallbacks with command timeouts.
  • Narrows timer discovery using a fixed marker and an end-anchored configurable tag.
  • Expands timer tests and updates the WSL migration checklist.

Confidence Score: 4/5

The PR is not yet safe to merge because timer discovery can still classify an unrelated process carrying both public identifiers as a drill timer and terminate it.

The reply on the earlier Python-filter thread correctly addresses Python-only false positives, but the separate public-marker thread remains unresolved: _drill_timer_pids trusts two reproducible command-line strings as identity, and _drill_timer_stop sends every resulting PID directly to kill.

Files Needing Attention: drill.sh

Important Files Changed

FilenameOverview
drill.shAdds marker-based timer discovery, dependency checks, exit-aware subprocess handling, and broader platform notification and audio fallbacks.
tests/suite_timer.shReworks timer-process checks to avoid matching the test shell and extends coverage of timer lifecycle behavior.
TODO.mdMarks earlier WSL checkpoints complete and documents the timer checkpoint’s expanded scope.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Timer command] --> B[Find marker-matching PIDs]
  B --> C[Find PIDs ending with timer tag]
  C --> D[Intersect PID sets]
  D --> E[Stop matching timers]
  A --> F[Launch Python countdown]
  F --> G[Notify through platform chain]
  G --> H[Play sound through platform chain]
  H --> I[Terminal bell fallback]

Reviews (11): Last reviewed commit: "Identify the timer by its own source, no..." | Re-trigger Greptile