Two small shell scripts that let an AI coding agent (Claude Code here) restart the Mac it is running on for a staged boot attempt, and tell the next session that the restart happened and why.
When the agent works on the machine it is testing (a Hackintosh, a kext, a boot config), every test ends with a reboot, and the reboot ends the session. The next session starts with no idea that a reboot just happened, which attempt it was for, or what to check. If something else restarted the machine in between (a panic, a guard rolling back a driver, a power cut), it should know that too.
legion-reboot.sh <id> "<reason>"refuses to restart while the project repo has uncommitted changes, writes the pending attempt (id, reason, time, boot time, repo commit, the check to run) topending.json, quits the open apps so they do not all come back at login, and runsshutdown -r now.reboot-detect.shis a Claude Code SessionStart hook. It compares the kernel boot time with the one the last session saw. Same boot: it prints nothing. New boot: it prints aREBOOT DETECTEDbanner with the pending attempt, a warning if more than one restart happened since it was recorded, the result of any boot guard that left a state file, and the number of crash reports since boot. Hook output lands in the session's context, so the agent reads it before your first message.
examples/claude-autostart.command (optional) opens Terminal at login and resumes the last Claude Code session with a prompt to run the check, so the work goes on without anyone typing.
In daily use on a Lenovo Legion 5 15IAX10 Hackintosh (macOS 26.7.1, 25G241) since 2026-10-17: by 2026-10-19 it had recorded 77 boot attempts for GPU, Wi-Fi and CPU driver work.
Needs macOS with the Xcode Command Line Tools (for /usr/bin/python3 and git).
git clone https://github.com/watchthelight-labs/legion-reboot ~/src/legion-reboot
~/src/legion-reboot/install.sh # links legion-reboot and reboot-detect into ~/.local/bin, copies the config
Settings live in ~/.config/legion-reboot/config (copied from config.example, every value documented there): the state folder (default ~/.cache/legion-reboot), the repo to check (default: the git repo of the current directory), the check command for the next session ({id} is replaced), the on-screen confirmation, which apps stay open, and the boot guards to report.
Then add the hook to ~/.claude/settings.json (or a project's .claude/settings.json):
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume|clear",
"hooks": [
{ "type": "command", "command": "/Users/you/.local/bin/reboot-detect", "timeout": 10 }
]
}
]
}
}Run reboot-detect once by hand: the first run records the boot time and says so.
For the autostart, ./install.sh --autostart installs claude-autostart.command and a LaunchAgent that runs it at login; set AUTOSTART_DIR in the config to the folder the agent works in. Its default arguments include --dangerously-skip-permissions, because that is what lets an unattended session do anything. Set AUTOSTART_CLAUDE_ARGS="--verbose" if you want the prompts.
printf '%s\n' "$PW" | legion-reboot boot-42 "new framebuffer patch" # sudo password on stdin
legion-reboot boot-42 "new framebuffer patch" </dev/null # with a sudoers rule (below)
legion-reboot boot-42 "retry" --force # skip the uncommitted-changes check
REBOOT_ASK=1 legion-reboot boot-42 "..." # ask on screen first
The attempt id is a free-form label; use whatever your notes use. Tell the agent in your project instructions to restart only through legion-reboot and to run the check when it sees REBOOT DETECTED.
After the restart, the next session starts with something like:
[reboot-detect] REBOOT DETECTED: this macOS booted 2026-10-19 13:23:13 EDT (uptime 2 min); the previous session saw a boot at 11:02:51.
[reboot-detect] pending attempt boot-42 requested 2026-10-19 13:21:40 EDT from repo 1dc999c: new framebuffer patch
[reboot-detect] this is that reboot; run /boot-check boot-42 now.
[reboot-detect] crash reports since boot: 0 (panic*.ips = kernel panic)
A guard is a boot-time daemon that watches a risky boot and rolls it back on its own when nobody confirms it. gpu-guard and wifi-guard work that way. Each keeps a pending.json with the attempt, its result and the reason. reboot-detect.sh prints one line per guard listed in REBOOT_GUARDS whose pending.json exists, and skips the rest, so without guards you see nothing extra.
-
Uncommitted changes stop it. With modified or staged files in the repo it exits with
REFUSEDand restarts nothing. Untracked files do not count.--forceskips the check. -
The password. It is read from stdin once (silently if stdin is a terminal), piped to
sudo -Sand unset. It never appears in an argument, a file,pending.jsonor the output.sudo -p ''keeps the prompt out of the log. If the shutdown fails (wrong password, no rule), it removes the pending attempt and exits 4, so the next session is not told about a restart that never happened. -
A sudoers rule is cleaner than handing the agent your password. Add this rule with
sudo visudo -f /etc/sudoers.d/shutdown(replaceyouwith your user name) and calllegion-rebootwith no password at all:you ALL=(root) NOPASSWD: /sbin/shutdown -r now * -
Quitting apps. By default every GUI app except Terminal, iTerm2 and Finder is asked to quit (unsaved work gets the usual prompt and that app stays open), and the login relaunch list is cleared.
REBOOT_QUIT_APPS=0turns that off;REBOOT_KEEP_APPSlists what stays. -
reboot-detect.shis read-only on the system; it writes only its state folder.
docs/HOW-IT-WORKS.md: the state files, how a reboot is detected, and the relaunch-list detail on macOS 26.
ISC, see LICENSE.