The morning routine is started by systemd right after boot and right after
resume, so its sync ran seconds before DHCP/DNS were up. Every such run failed
outright — and said this:
Workout sync push FAILED for 6 workout(s): network error reading
screen-locker-sync/devices — a 403 here means the token lacks
contents:write on kuhyx/syncs
Both halves of that were wrong. It was not a 403, and the token was fine: the
same six workouts pushed successfully by hand minutes later on the same token,
which reports push=true on the repo. The message cost a real investigation
chasing a permissions bug that did not exist.
- _sync_retry.with_sync_retry wraps the three sync call sites in a bounded
retry (4 attempts, 2s/4s/8s backoff — ~14s, enough for the network to come
up, short enough not to drag the routine). Verified offline in a network
namespace: three retries, then an honest give-up.
- Any GitHubSyncError retries, deliberately including RepoNotFoundError.
Classifying transient vs permanent would couple us to which exception
crdt_sync chains where; a few wasted seconds on a genuinely bad token is the
cheaper mistake.
- The push failure log now reports what actually happened. The contents:write
hint survives only for RepoNotFoundError, which is the one error that
actually implicates the repo or the token.
Retry at the application layer rather than ordering the unit after
network-online.target: the same race hits on resume, where boot ordering does
not apply, and these are user units that cannot depend on a system target.
The tests' autouse no-sleep fixture patches the module-local `sleep` name
rather than `_sync_retry.time.sleep` — the latter mutates the shared time
module object and silently disabled sleep for the whole suite, breaking an
unrelated temperature timeout test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .github/workflows | ||
| docs | ||
| screen_locker | ||
| scripts | ||
| stronglift_replacement | ||
| .gitignore | ||
| .mcp.json | ||
| .pre-commit-config.yaml | ||
| CLAUDE.md | ||
| early-bird-workout-check.timer | ||
| install_autostart.sh | ||
| install_systemd.sh | ||
| pyproject.toml | ||
| README.md | ||
| remove_autostart.sh | ||
| remove_systemd.sh | ||
| requirements.txt | ||
| run.sh | ||
| workout-locker.service | ||
screen-locker
Tkinter/systemd screen locker with workout tracking, sick-day management, and wake-alarm integration. It enforces a workout cadence by locking the screen until a RunnerUp-verified (or explicitly justified) workout is logged.
MCP server (Claude Code integration)
screen-locker exposes a read-only MCP server (screen_locker._mcp) so
Claude Code and its subagents can query workout compliance and the
lock-decision state through typed tools — without shelling out to the
screen-locker-status CLI or opening the Tk window.
- Read-only tools:
get_status(full status snapshot),get_summary(one-line i3blocks summary +ok/warn/lockstate word),explain_lock(why the screen is / isn't locked right now),get_flags(the individual boolean lock-decision predicates for today). - There are no write/action tools, by design. Nothing here logs a workout or mutates state — workouts are logged only from RunnerUp-verified TCX data, never from a caller's claim. No tool exposes the sync token or any HMAC key.
The mcp SDK is an optional dependency (pip install -e '.[mcp]'), kept out of
the CLI/systemd system-python path. One-time setup of the dedicated venv that
Claude Code spawns:
./scripts/setup_mcp.sh
Registration lives in the checked-in .mcp.json (project scope).
Restart Claude Code in this repo and approve the project MCP-server prompt for it
to load. Verify with claude mcp list.