You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Problem (revised — this is a deployment-model regression, not a nice-to-have)
Before #25, lock-sync clients had zero footprint: lock-fanout SSHed directly to each host identified via Synergy (list-clients, with optional ~/.config/lock-sync/config username overrides) and ran pmset displaysleepnow — a macOS built-in. No client ever needed a git clone of this repo, and no client ever needed bin/install run on it. Only the controller (the machine running lock-watcher) needed the repo checked out.
#25 broke that model silently: lock-fanout now invokes lock-guard, a script that only exists on a machine after bin/install has been run there from a checkout of this repo. Every client that has never cloned lock-sync — which, before #25, was every client, by design — fails with ssh_exit=127 and never locks. This isn't "clients can drift out of sync," it's "the client-side install requirement didn't exist before and now silently does, with no mechanism to satisfy it remotely." It was caught only because the controller's operator happened to test locking manually and noticed clients stopped responding.
Required fix: remote install/upgrade as part of the lock flow itself
Per repo owner: the process needs to become roughly:
bin/install runs on the controller (as today).
Controller locks (lock-watcher fires, as today).
The lock-triggered service identifies clients (via list-clients, as today) and fans out to each — but the fan-out must now also ensure the client is properly provisioned, not just invoke lock-guard and hope it's there.
On each client: lock-guard (and any other client-side files/config lock-sync comes to need) is installed or upgraded if missing or stale — without requiring the client to have ever cloned the repo. This likely means the controller pushes the needed script(s) over SSH (e.g. ssh client 'cat > /tmp/lock-guard && chmod +x /tmp/lock-guard' fed from the controller's local copy, or similar) rather than expecting git clone + bin/install to have happened out-of-band on that machine.
Only after the client is confirmed provisioned does step 5 (today's behavior) happen: lock-guard runs its suppression checks and locks the display if allowed.
This keeps the "clients need zero setup" property that existed before #25 and that #26 (this issue) exists to restore — not just "keep already-installed clients in sync," but "clients never need to be installed at all; the controller carries and pushes what they need, on demand, as part of every lock cycle" (or cached/skipped when already up to date, to avoid re-pushing on every single lock).
Non-goals reminder
Per this project's CLAUDE.md, this repo is a deliberate simplification of lock-sync-old, which failed by trying to do too much (bidirectional sync, remote wake/unlock). Pushing lock-guard to clients on demand is push-only and lock-triggered — it does not reintroduce bidirectional sync, remote wake, or persistent client-side agents. Scope stays narrow: "the controller ensures each client has what it needs to correctly handle a lock command, pushed at lock time," nothing broader (no general remote code execution framework, no persistent client daemon beyond what already exists for the controller's own lock-watcher).
Open questions to resolve during design
What gets pushed, and how much. Just lock-guard itself (a single self-contained script — cheap to cat over SSH), or does client-side provisioning eventually need more (a config file, a version marker)? Keep this minimal — pushing one script's contents over an SSH command is cheap and doesn't require the client to have git, curl, or any package manager.
Staleness detection. How does the controller know a client already has an up-to-date lock-guard and can skip the push (to avoid re-pushing the same file over SSH on every single lock event, which adds latency and log noise to the lock-critical path)? Options: a checksum/version comparison before push, or simply always push (cheap enough that idempotent overwrite might be simpler than staleness tracking) — needs real latency measurement given lock-fanout already runs sequentially per client.
Ordering and latency in the lock-critical path. Provision-then-lock is now two SSH round-trips per client instead of one. Given lock-fanout already loops sequentially over clients, this doubles worst-case latency for the whole fan-out. Consider whether provisioning can be a separate, non-lock-triggered step (e.g. run once at controller boot / on bin/install, or on a light periodic check) rather than inline on every lock event — the repo owner's proposed flow puts it inline (step 4 before step 5 on every lock), but that tradeoff should be made explicit and possibly reconsidered during design, not just accepted from the first sketch of the idea.
Failure mode when a client can't be provisioned. Same "log and move on" philosophy as lock-fanout's existing ssh_exit handling — a client that can't be provisioned this cycle shouldn't block others, and should probably fail toward the pre-Suppress screen lock during meetings (lock-guard) #25 behavior (bare pmset displaysleepnow) rather than fail toward "never lock," to avoid quietly reintroducing Suppress screen lock during meetings (lock-guard) #25's exact failure mode on any client the provisioning step itself can't reach.
Trust boundary. The controller now pushes and executes arbitrary(-ish) script content on clients over SSH, not just a fixed command (pmset/lock-guard) that's already installed there. This is a meaningfully larger trust step than what existed before, even though it rides on the same SSH key trust already in place. Worth being explicit about in the design, even if the conclusion is "this is an acceptable extension of the existing trust model since these are all machines the same person controls."
Why this matters
This is not a future nice-to-have — it's closing a real deployment-model regression introduced by #25 that made every previously-zero-footprint client silently non-functional until manually and individually fixed by an operator physically or remotely re-installing on each one.
Problem (revised — this is a deployment-model regression, not a nice-to-have)
Before #25,
lock-syncclients had zero footprint:lock-fanoutSSHed directly to each host identified via Synergy (list-clients, with optional~/.config/lock-sync/configusername overrides) and ranpmset displaysleepnow— a macOS built-in. No client ever needed a git clone of this repo, and no client ever neededbin/installrun on it. Only the controller (the machine runninglock-watcher) needed the repo checked out.#25 broke that model silently:
lock-fanoutnow invokeslock-guard, a script that only exists on a machine afterbin/installhas been run there from a checkout of this repo. Every client that has never cloned lock-sync — which, before #25, was every client, by design — fails withssh_exit=127and never locks. This isn't "clients can drift out of sync," it's "the client-side install requirement didn't exist before and now silently does, with no mechanism to satisfy it remotely." It was caught only because the controller's operator happened to test locking manually and noticed clients stopped responding.Required fix: remote install/upgrade as part of the lock flow itself
Per repo owner: the process needs to become roughly:
bin/installruns on the controller (as today).lock-watcherfires, as today).list-clients, as today) and fans out to each — but the fan-out must now also ensure the client is properly provisioned, not just invokelock-guardand hope it's there.lock-guard(and any other client-side files/config lock-sync comes to need) is installed or upgraded if missing or stale — without requiring the client to have ever cloned the repo. This likely means the controller pushes the needed script(s) over SSH (e.g.ssh client 'cat > /tmp/lock-guard && chmod +x /tmp/lock-guard'fed from the controller's local copy, or similar) rather than expectinggit clone+bin/installto have happened out-of-band on that machine.lock-guardruns its suppression checks and locks the display if allowed.This keeps the "clients need zero setup" property that existed before #25 and that #26 (this issue) exists to restore — not just "keep already-installed clients in sync," but "clients never need to be installed at all; the controller carries and pushes what they need, on demand, as part of every lock cycle" (or cached/skipped when already up to date, to avoid re-pushing on every single lock).
Non-goals reminder
Per this project's CLAUDE.md, this repo is a deliberate simplification of
lock-sync-old, which failed by trying to do too much (bidirectional sync, remote wake/unlock). Pushinglock-guardto clients on demand is push-only and lock-triggered — it does not reintroduce bidirectional sync, remote wake, or persistent client-side agents. Scope stays narrow: "the controller ensures each client has what it needs to correctly handle a lock command, pushed at lock time," nothing broader (no general remote code execution framework, no persistent client daemon beyond what already exists for the controller's ownlock-watcher).Open questions to resolve during design
lock-guarditself (a single self-contained script — cheap tocatover SSH), or does client-side provisioning eventually need more (a config file, a version marker)? Keep this minimal — pushing one script's contents over an SSH command is cheap and doesn't require the client to have git, curl, or any package manager.lock-guardand can skip the push (to avoid re-pushing the same file over SSH on every single lock event, which adds latency and log noise to the lock-critical path)? Options: a checksum/version comparison before push, or simply always push (cheap enough that idempotent overwrite might be simpler than staleness tracking) — needs real latency measurement givenlock-fanoutalready runs sequentially per client.lock-fanoutalready loops sequentially over clients, this doubles worst-case latency for the whole fan-out. Consider whether provisioning can be a separate, non-lock-triggered step (e.g. run once at controller boot / onbin/install, or on a light periodic check) rather than inline on every lock event — the repo owner's proposed flow puts it inline (step 4 before step 5 on every lock), but that tradeoff should be made explicit and possibly reconsidered during design, not just accepted from the first sketch of the idea.lock-fanout's existingssh_exithandling — a client that can't be provisioned this cycle shouldn't block others, and should probably fail toward the pre-Suppress screen lock during meetings (lock-guard) #25 behavior (barepmset displaysleepnow) rather than fail toward "never lock," to avoid quietly reintroducing Suppress screen lock during meetings (lock-guard) #25's exact failure mode on any client the provisioning step itself can't reach.pmset/lock-guard) that's already installed there. This is a meaningfully larger trust step than what existed before, even though it rides on the same SSH key trust already in place. Worth being explicit about in the design, even if the conclusion is "this is an acceptable extension of the existing trust model since these are all machines the same person controls."Why this matters
This is not a future nice-to-have — it's closing a real deployment-model regression introduced by #25 that made every previously-zero-footprint client silently non-functional until manually and individually fixed by an operator physically or remotely re-installing on each one.