From 28779586eb37fed6f03b196e673eb687dd2000cf Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 17 Jul 2026 14:02:33 +0200 Subject: [PATCH] fix(wellbeing): symlink focus-mode-daemon so edits actually deploy install_focus_mode_daemon.sh copied the script to /usr/local/bin, so the deployed daemon went stale the instant the repo file was edited: what you run stops matching what you read, with nothing to tell you. That is not hypothetical - the zombie-detection fix in 22e22c1 sat in the repo while a months-old copy at /usr/local/bin/focus-mode-daemon went on entering GAMING mode and killing browsers. It only surfaced because the deployed copy was checked by hand. Link instead of copy, so editing the repo file IS deploying it. Also chmod +x the source, since a symlink takes its executability from the target rather than from an install mode. uninstall now guards with [[ -e || -L ]] rather than [[ -f ]]: both -f and -e follow the link and report false when the target is gone, which is exactly the case (repo moved or deleted) where a stale link most needs clearing. The link resolves into the user's checkout, so /usr/local/bin/... is only as trustworthy as that path. Fine here - focus-mode runs as a systemd *user* service, crossing no privilege boundary. Not a pattern to copy for anything root runs. Verified: stat -L reports the same inode (92274745) for the deployed path and the repo file; appending a marker to the repo file makes it appear at /usr/local/bin/focus-mode-daemon immediately, with no reinstall; a link to a missing target is now removed by uninstall where [[ -f ]] skipped it. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_013rN2usTS3pYLJqeVg2ZpgR --- ...cus-mode-installer-symlink-2026-07-17.json | 56 +++++++++++++++++++ .../install_focus_mode_daemon.sh | 24 ++++++-- 2 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 docs/superpowers/evidence/focus-mode-installer-symlink-2026-07-17.json diff --git a/docs/superpowers/evidence/focus-mode-installer-symlink-2026-07-17.json b/docs/superpowers/evidence/focus-mode-installer-symlink-2026-07-17.json new file mode 100644 index 00000000..9eff978d --- /dev/null +++ b/docs/superpowers/evidence/focus-mode-installer-symlink-2026-07-17.json @@ -0,0 +1,56 @@ +{ + "intent": "Make editing focus_mode_daemon.py actually change what runs. The installer copied the script to /usr/local/bin/focus-mode-daemon, so the deployed daemon silently went stale the moment the repo file was edited - a fix for phantom Steam detection (commit 22e22c1) sat in the repo while a months-old copy kept killing browsers.", + "scope": [ + "linux_configuration/scripts/periodic_background/digital_wellbeing/install_focus_mode_daemon.sh", + "Non-goal: any change to focus_mode_daemon.py itself (done separately in 22e22c1).", + "Non-goal: the other install scripts in this tree that may copy rather than link; not audited here." + ], + "changes": [ + "install_daemon(): replaced `install -m 755 ` with `ln -sfn `, so /usr/local/bin/focus-mode-daemon is a symlink into the checkout and editing the repo file IS deploying it.", + "Added `chmod +x` on the source, since a symlink takes its executability from the target rather than from an install mode.", + "uninstall_daemon(): widened the removal guard from `[[ -f ]]` to `[[ -e || -L ]]`. -f and -e both follow symlinks and report false for a link whose target is gone, which is exactly the case (repo moved or deleted) where a stale link most needs clearing.", + "Documented in-comment why a link and not a copy, and noted that the target lives in the user's checkout - acceptable because focus-mode runs as a systemd *user* service and so crosses no privilege boundary." + ], + "verification": [ + { + "command": "./install_focus_mode_daemon.sh install", + "result": "pass", + "evidence": "'Linking /usr/local/bin/focus-mode-daemon -> /home/kuhy/testsAndMisc/.../focus_mode_daemon.py'. ls -la confirms lrwxrwxrwx pointing at the checkout; before the change it was '-rwxr-xr-x root root 15760' (a copy)." + }, + { + "command": "stat -L -c '%i %n' /usr/local/bin/focus-mode-daemon focus_mode_daemon.py", + "result": "pass", + "evidence": "Both report inode 92274745 - the deployed command IS the repo file. (Note: plain `stat` without -L reports the symlink's own inode and appears to disagree; -L is required.)" + }, + { + "command": "staleness canary: append a marker to the repo file, grep the deployed path, revert", + "result": "pass", + "evidence": "grep -c marker /usr/local/bin/focus-mode-daemon -> 1 immediately after the edit, 0 after revert; git diff --stat clean afterwards. Proves an edit deploys with no reinstall step." + }, + { + "command": "focus-mode-daemon (deployed command, 8s)", + "result": "pass", + "evidence": "'Focus Mode Daemon starting...' then 'Browser detected - entering BROWSING mode'. Runs correctly through the symlink." + }, + { + "command": "dangling-link guard check", + "result": "pass", + "evidence": "For a link to a nonexistent target: [[ -f ]] is false (old code would leave the stale link behind), [[ -e || -L ]] is true (now removed)." + }, + { + "command": "pre-commit run --files (in a clean worktree)", + "result": "pass", + "evidence": "Run from a temporary git worktree because the main tree carries unrelated uncommitted brother_printer work whose tests fail." + } + ], + "risks": [ + "/usr/local/bin/focus-mode-daemon now resolves into a user-writable checkout, so anything invoking it as root would execute user-writable code. Acceptable here: focus-mode runs as a systemd user service. Do not copy this pattern for anything root runs.", + "The link breaks if the repo is moved, renamed, or deleted; re-run the installer after any such move. Uninstall now clears a dangling link rather than skipping it.", + "Running the installer to verify also enabled and started focus-mode.service, which had not previously been running. User confirmed on 2026-07-17 to leave it enabled." + ], + "rollback": [ + "git revert this commit and re-run ./install_focus_mode_daemon.sh install to return to a copied binary.", + "After rollback, remember the staleness footgun is back: every future edit to focus_mode_daemon.py requires re-running the installer before the change takes effect.", + "Verify with: ls -la /usr/local/bin/focus-mode-daemon (expect a regular file, not a symlink)." + ] +} diff --git a/linux_configuration/scripts/periodic_background/digital_wellbeing/install_focus_mode_daemon.sh b/linux_configuration/scripts/periodic_background/digital_wellbeing/install_focus_mode_daemon.sh index 7f13a8b9..b059e6d9 100755 --- a/linux_configuration/scripts/periodic_background/digital_wellbeing/install_focus_mode_daemon.sh +++ b/linux_configuration/scripts/periodic_background/digital_wellbeing/install_focus_mode_daemon.sh @@ -73,12 +73,21 @@ install_daemon() { exit 1 fi - # Install the daemon script - msg "Installing daemon script to $INSTALL_PATH" + # Symlink rather than copy. A copy goes stale the instant the repo file is + # edited, so the daemon being run silently stops matching the daemon being + # read - which is exactly how a fix for phantom Steam detection sat in the + # repo while the months-old copy at $INSTALL_PATH went on killing browsers. + # A link means editing the repo file IS deploying it. + # + # The target lives in the user's checkout, so $INSTALL_PATH is only as + # trustworthy as that path. That is fine here: focus-mode runs as a systemd + # *user* service, so no privilege boundary is crossed. + chmod +x "$DAEMON_SCRIPT" + msg "Linking $INSTALL_PATH -> $DAEMON_SCRIPT" if [[ $EUID -eq 0 ]]; then - install -m 755 "$DAEMON_SCRIPT" "$INSTALL_PATH" + ln -sfn "$DAEMON_SCRIPT" "$INSTALL_PATH" else - sudo install -m 755 "$DAEMON_SCRIPT" "$INSTALL_PATH" + sudo ln -sfn "$DAEMON_SCRIPT" "$INSTALL_PATH" fi # Create systemd user directory @@ -159,8 +168,11 @@ uninstall_daemon() { # Reload daemon systemctl --user daemon-reload 2>/dev/null || true - # Remove installed script - if [[ -f "$INSTALL_PATH" ]]; then + # Remove installed script. -L as well as -e: $INSTALL_PATH is a symlink into + # the checkout, and -e alone reports false for one whose target is gone - + # exactly the case (repo moved or deleted) where the stale link most needs + # clearing. + if [[ -e "$INSTALL_PATH" || -L "$INSTALL_PATH" ]]; then msg "Removing daemon script..." if [[ $EUID -eq 0 ]]; then rm -f "$INSTALL_PATH"