Multi-account: one badge, every OneDrive account - #4
Merged
Merged
Conversation
onedrive-status.py gains --confdir, to report status for one account by config directory, and --list-accounts, to enumerate the accounts on the machine. Each account's config directory is read out of its own systemd unit's ExecStart argv[], never guessed from the instance name, so an instance pointed at an unrelated directory still resolves correctly. Enumeration is systemctl list-units plus a sweep of the enablement symlinks, because list-units reports only loaded units and list-unit-files never expands template instances. With no systemd at all it falls back to the single default account. Each account gets its own state directory, so the lock is per-account as well as the cache, keyed on the service and the canonicalized config directory. The default account's JSON output, cache path and lock path are unchanged, so today's single-account behaviour is byte-identical. The QML layer is unchanged and does not use the new flags yet; it is redesigned separately. Reviewed through the full gauntlet at Tier 2: /code-review high plus a convergence pass, /security-review (no high or medium findings), /simplify, three persona agents, and cross-family passes from codex and grok. 34 confirmed findings fixed. See the reports on PR #1. Known follow-ups, both flagged on the PR: --resume-unit is required by the QML design and does not exist yet, and docs/ARCHITECTURE.md's read-surface list is now stale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
--resume-unit names the transient systemd resume unit to inspect, so resumeAt can be correct per account instead of every account reporting the one fixed timer. It defaults to the legacy omaonedrive-resume for the plain service, so an in-flight timer survives an upgrade; without the flag every other account reports no resume time rather than borrowing that one. The value is validated as a systemd unit name, capped at 255 bytes, and a trailing .timer is normalised rather than refused. Neither a service nor a resume unit name may begin with '-'. Both are passed to systemctl as positional arguments, so such a value was read as its --machine or --host option, the latter making systemctl attempt an outbound connection. Confirmed against the real binary; the service gate had the same hole beforehand. Both gates now share one character class and one length rule, and --list-accounts no longer returns before validation runs. A config directory whose own name contains ' - ' is now rejoined correctly rather than resolving to a shorter path that happened to exist, which was another account's directory. docs/ARCHITECTURE.md states the helper's read surface as a closed list and rests its privacy claim on that completeness. The list was missing the config file the helper parses directly, the onedrive --version call, the OMAONEDRIVE_UNIT_ROOTS override that replaces the whole unit-directory list, and the account's own status-cache.json. Seven further claims across that file and TESTING.md overstated what the code guarantees; all are corrected. Reviewed over three rounds: /code-review xhigh, /security-review twice (no high or medium findings), /simplify, a docs-accuracy pass, and two rounds of cross-family review from codex and grok, the second re-reviewing the first round's fixes. 27 findings fixed. Seven further confirmed findings against already-merged code are listed on the PR as follow-ups. The no-flag JSON output remains byte-identical to the pre-multi-account version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The bar becomes multi-account: the helper discovers every onedrive systemd unit (plain and template instances), and the widget polls them round-robin, aggregates them worst-first into one badge, and drives every action -- pause, resume, timed pause, reauth, resync repair, folder, storage -- against the selected account's own service, config directory and resume timer. One account keeps exactly the single-account behaviour, commands and all. Coordination: one status poll at a time across the fleet, with a startup ramp that gives unpolled accounts priority without letting a broken one monopolise; one cloud check at a time, deduplicated against both the queue and the check in flight; desktop notifications coalesced per polling burst, grouped popups opening the worst account. IPC gains accounts() and selectAccount(); every gesture and notification click selects the account it is about before acting. Worst-first is the user's stated rule for the badge, pause included: every account must be working before the bar looks normal. Hardening that fell out of five adversarial review passes: every process settles exactly once per invocation -- on exit, on failure to start (real Quickshell never emits exited for those), or by watchdog -- so a missing python3, a wedged helper or a hung systemctl cannot freeze the fleet or kill Pause for the session. A pause holds its optimistic state until a poll STARTED after the control confirms it. Every helper reply is stamped with the config directory it actually read, so the startup seed poll can never attach the default account's data to another unit's name. The helper allowlists LoadState=loaded with a real ExecStart, and survives any malformed cache. Verification: 124 node tests, 263 harness checks driving the real coordinator headless, a QML-to-real-helper contract check, helper suites, and a qmllint gate for the two files nothing else parses. Every function in Service.qml and Account.qml fails the suite when emptied; every condition in Model.js fails when inverted. Full finding ledger on the branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ErMhXzfZ86BcJuhhgb242
…miss (#3 follow-up) The invert-every-if sweep over Service.qml and Account.qml finally ran to completion. Eleven of 117 conditions survived; none were dead code, and all are now pinned by harness checks: the settings NaN-guard in both directions, error precedence on the facade, the discovery failure-to-start hook firing only on failure to start, the timed pause on an already-stopped account, the settle loop's persistence under a blocked slot, the hung (not failed) systemd-run watchdog and its final recovery message, all three cloud-check completion messages plus the routine-poll silence gate, and the account-level deferred cloud request. Test-only: 299 harness checks (was 263), no runtime file changed. Final sweep state -- zero of 117 conditions in the QML and zero of 97 in Model.js invert undetected; all 65 QML functions fail the suite when emptied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ErMhXzfZ86BcJuhhgb242
Brings salemsayed/omaonedrive releases 1.5.5 and 1.5.6 into the fork: durable notification clicks via omarchy-notification-send's persisted --exec hint (extended so the click carries its ACCOUNT as an argv element, received by the new openAccount/repairAccount IPC functions), plain-text rendering at every inherited boundary including the multi-account tooltip, the CI workflow, the CHANGELOG (with this fork's 1.6.0 entry on top), the 4.0.1 VM test report, and the reliable partial-output timeout regression test. The merge was assembled by hand-porting each upstream hunk into the multi-account structure and verified by the full suite (125 node tests, 300 harness checks, contract check), eight targeted mutations of the ported mechanism, and an adversarial four-auditor review of the port against upstream's own diff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ErMhXzfZ86BcJuhhgb242
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3 — this implements the design proposed there: discovery from each unit's own
ExecStartrather than a naming convention, worst-of-N aggregation on the badge, per-account actions behind a selector, and a single-account install kept byte-for-byte on today's behaviour.OmaOneDrive becomes multi-account while keeping a one-account machine byte-for-byte on today's behaviour.
What it does
onedrive-status.py --list-accountsdiscovers every OneDrive systemd unit — the plainonedrive.serviceand anyonedrive@<instance>template units — reading each unit's real--confdirout of its own ExecStart (allowlisted toLoadState=loadedwith a non-empty ExecStart, so a masked/error/stub unit can never alias onto the default account's token and cache).omaonedrive-resume@<instance>timer, resume, reauth, resync repair, open folder, storage check — runs against the selected account's own service, config directory, and resume unit. Cloud checks are serialized fleet-wide (one 30s check at a time), deduplicated against both the queue and the check in flight.--exechint (built on 1.5.5/1.5.6's mechanism — newopenAccount/repairAccountIPC functions receive it). Scripts getaccountsandselectAccountIPC.exitedfor an executable that cannot start), so a missing python3, a wedged helper, or a hungsystemctlcan no longer freeze polling or disable Pause; a pause holds its optimistic state until a poll started after the control confirms it; every helper reply is stamped with the confdir it actually read and mismatches are refused, so the startup poll can never attach the default account's data to another unit's name; the cache survives any malformed content.Compatibility
omaonedrive-resumetimer name is kept for the plain service, so an in-flight pause survives the upgrade.How it's verified
Service.qml/Account.qmlagainst stub Quickshell types (scheduler, cloud semaphore, discovery reconciliation, notification broker, watchdogs, per-account command vectors).--list-accountsoutput through the real QML and executes every produced command against the real helper.Service.qml/Account.qmlfails the suite when emptied; everyifcondition there and inModel.jsfails when inverted.tests/runskips the Qt-dependent layers cleanly, so your CI (node + jq, no Qt) runs green.omarchy plugin add, exercised over IPC and by hand.Full development history (5 adversarial review passes, ~40 findings, all fixed and pinned) is on the fork: mikebenner/omaonedrive.
🤖 Generated with Claude Code