fix(deck): unmask sleep + add the reachability watchdog that makes it safe - #343
fix(deck): unmask sleep + add the reachability watchdog that makes it safe#343mabry1985 wants to merge 2 commits into
Conversation
… black screen The deck-deploy skill told operators to mask the sleep targets so deep-suspend couldn't take the Deck offline during remote ops. Applied on the Deck 2026-07-22; reverted today after it produced the failure the 2026-06-21 notes predicted. Game Mode blanks the panel *before* calling suspend, so with suspend.target masked the suspend fails while the screen is already off — and since nothing suspended, no resume ever turns the panel back on. The Deck sits awake on a black screen with no wake path. Replace the recommendation with the revert steps and point at the actual fix for the underlying problem: a resume hook that restarts tailscaled + protopen-runtime after suspend, rather than preventing sleep at all. Device already reverted: the four targets are unmasked (now `static`) and the mask paths dropped from /etc/atomic-update.conf.d/protopen-keep.conf. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
WalkthroughThe deep-suspend guidance now explains S3 disconnect behavior, removes sleep-target masking instructions, documents black-screen recovery commands, and recommends a resume hook that restarts ChangesDeep-suspend guidance
Estimated code review effort: 1 (Trivial) | ~3 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/skills/deck-deploy/SKILL.md:
- Around line 110-112: The resume-hook instructions must restart the
user-managed tailscaled.service and protopen-runtime.service in the deck
user-manager context, rather than treating them as root system services. Update
the system-sleep hook guidance to use the deck machine target and execute after
resume networking is available, before relying on the proxy.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: b2a92a1a-e9bc-46a2-a4e1-679152f0879f
📒 Files selected for processing (1)
.claude/skills/deck-deploy/SKILL.md
| The right way to keep the Deck reachable across a suspend is a resume hook | ||
| (`/usr/lib/systemd/system-sleep/`) that restarts tailscaled + `protopen-runtime`, | ||
| not preventing sleep. |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 5 \
'/usr/lib/systemd/system-sleep|systemctl --user|systemctl --machine|tailscaled(\.service)?|protopen-runtime(\.service)?' \
--glob '*.sh' --glob '*.service' --glob '*.md' .Repository: protoLabsAI/protoPen
Length of output: 27658
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== SKILL.md excerpt =="
cat -n .claude/skills/deck-deploy/SKILL.md | sed -n '1,150p'
echo
echo "== related files if present =="
for f in .claude/skills/deck-deploy/*.sh .claude/skills/deck-deploy/*.service; do
[ -e "$f" ] && { echo "--- $f"; cat -n "$f" | sed -n '1,220p'; }
done
echo
echo "== broad sleep references =="
rg -n -C 3 'system-sleep|sleep.target|suspend.target|hibernate.target|hybridsleep|tailscaled|protopen-runtime' .Repository: protoLabsAI/protoPen
Length of output: 25741
Make the resume hook restart the systemd user services under deck.
.config/systemd/user/ installs tailscaled.service and protopen-runtime.service as user units, while /usr/lib/systemd/system-sleep/ runs as root. A resume hook needs the deck user-manager context, for example systemctl --machine deck@deck restart tailscaled.service protopen-runtime.service, and should run after the resume network is available before relying on the proxy.
🧰 Tools
🪛 SkillSpector (2.4.4)
[warning] 108: [PE2] Sudo/Root Execution: Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
Remediation: Avoid sudo/root unless strictly required. Prefer least-privilege patterns. If elevation is needed, document the justification and scope.
(Privilege Escalation (PE2))
[warning] 117: [PE2] Sudo/Root Execution: Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
Remediation: Avoid sudo/root unless strictly required. Prefer least-privilege patterns. If elevation is needed, document the justification and scope.
(Privilege Escalation (PE2))
[error] 116: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.
(Tool Misuse (TM1))
[error] 116: [YR1] YARA rule 'agent_skill_destructive_autonomous_actions': Autonomous destructive filesystem, shell history, or repository actions in AI agent skills [agent_skills]: YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
Remediation: Remove the malware payload or compromised file entirely. Investigate how it entered the skill and audit all other artifacts for additional indicators of compromise.
(YARA Match (YR1))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/skills/deck-deploy/SKILL.md around lines 110 - 112, The resume-hook
instructions must restart the user-managed tailscaled.service and
protopen-runtime.service in the deck user-manager context, rather than treating
them as root system services. Update the system-sleep hook guidance to use the
deck machine target and execute after resume networking is available, before
relying on the proxy.
Completes the sleep revert. Sleep was masked on 2026-07-22 because deep-suspend freezes wifi, tailscaled and the runtime container and they don't reliably come back — leaving the Deck unreachable. Masking traded that for a worse failure (a black screen with no wake path), so unmasking needs the real fix underneath it. `protopen-watchdog.timer` + `.sh` probe the tailnet and the runtime and repair only what's actually broken: - REPAIR, DON'T RESTART — a runtime restart costs ~70s and kills in-flight work, so a healthy tick does nothing and logs nothing. - A boot grace (180s) plus a one-tick wait before acting keeps it from fighting a service that is merely still starting, or thrashing on a transient blip. - Skips any unit the operator has deliberately disabled. Why a user timer rather than a /usr/lib/systemd/system-sleep hook: that directory is root-owned on the rootfs and needs an atomic-update keep-list entry to survive an OS update — precisely the fragility that produced the black screen. install.sh deliberately lives entirely in /home and this follows suit. The timer is realtime, so a tick elapsed during suspend runs on resume; it therefore covers resume *and* wifi drops / container crashes, which a resume-only hook would not. Verified live: `systemctl --user stop protopen-runtime` (an explicit stop, which suppresses the unit's own Restart=on-failure) → watchdog logged "not active — starting it" 10s later → serving again within 40s. The tailscaled repair branch is deliberately untested in place, since breaking the tailnet would sever the only access path to the Deck. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fixes the black-screen failure on the Deck, and puts the right mechanism underneath it.
The bug
The
deck-deployskill recommended masking the sleep targets so deep-suspend couldn't take the Deck offline mid-remote-op. Applied to the live Deck 2026-07-22. It produced exactly the failure the 2026-06-21 notes predicted.Mechanism: Game Mode blanks the panel before calling suspend. With
suspend.targetmasked the suspend call fails — but the screen is already off, and since nothing suspended, no resume ever turns the panel back on. The Deck sits awake on a black screen with no wake path.The 2026-07-22 change dismissed the earlier warning as a separate "display-wake" concern distinct from S3 suspend. They're the same failure.
Part 1 — sleep unmasked (already applied to the device)
systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target→ all four back tostatic/etc/atomic-update.conf.d/protopen-keep.conf(backup kept; alfa/hackrf/btusb entries untouched)The masking was only ever a manual step from the skill doc — not in
install.sh/bootstrap.sh— so no fresh install re-applies it.Part 2 — the watchdog that makes sleep safe
Unmasking alone would restore the original problem: suspend freezes wifi, tailscaled and the runtime container, and they don't reliably return. So
protopen-watchdog.timer+.sh(user scope, installed and enabled bydeck/install.sh) probe the tailnet and runtime every 30s and repair only what's broken:Why a user timer, not a
/usr/lib/systemd/system-sleephook. That directory is root-owned on the rootfs and needs an atomic-update keep-list entry to survive an OS update — the exact fragility that produced this bug.install.shdeliberately lives entirely in /home; this follows that rule, so there's nothing to preserve. The timer is realtime, so a tick that elapses during suspend runs as soon as the Deck wakes — it doubles as the post-resume trigger, and additionally covers wifi drops and container crashes that a resume-only hook would miss.Verification
Deployed and enabled on the Deck. Dry run on a healthy system: silent, rc=0.
Repair path tested live —
systemctl --user stop protopen-runtime(an explicit stop, which suppresses the unit's ownRestart=on-failure, so recovery is attributable to the watchdog):Not verified: the tailscaled repair branch. Breaking the tailnet would sever the only access path to the Deck, so it's left to a real resume to exercise.
Suspend/resume itself is for the operator to confirm at the device — I deliberately didn't trigger a suspend, since that drops SSH and needs a physical wake.
🤖 Generated with Claude Code