v1: shared client + calendar countdown + CI status integrations - #1
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 249c564e04
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if runs is not None: # None = 304/no-change/error -> keep cached state | ||
| state_cache[repo] = evaluate_runs(repo, runs, now, | ||
| c["stale_queued_minutes"]) |
There was a problem hiding this comment.
Re-evaluate queued runs after 304 responses
When a run is first fetched before stale_queued_minutes elapses, the cached RepoState records it as non-stuck; subsequent 304 responses skip evaluate_runs, even though the run's locally computed age continues increasing. A run that remains queued can therefore never produce the promised stuck alert until GitHub changes the representation—typically when it leaves the queued state. Cache the runs or queued timestamps and re-evaluate their age on every poll.
Useful? React with 👍 / 👎.
| resp = requests.get(url, headers=headers, | ||
| params={"per_page": 10}, timeout=(5, 15)) |
There was a problem hiding this comment.
Preserve failures that fall outside the first results page
For repositories with more than ten recent runs, a failed workflow disappears from this response as soon as other workflows produce ten newer runs. run_once then replaces that repository's cached state using only this truncated page and clears the alert even though the offending workflow never went green. Paginate, query each workflow, or retain per-workflow state until a newer run for that workflow is observed.
Useful? React with 👍 / 👎.
|
|
||
|
|
||
| def _element(text: str, color: str, timeout_s: int) -> dict: | ||
| return {"id": "ci", "type": "text", "text": text, "font": "normal", |
There was a problem hiding this comment.
Sanitize CI text before sending it to the display
When a GitHub workflow name contains Unicode, such as an emoji or non-Latin text, this element forwards it unchanged even though the device text API accepts printable ASCII only. The resulting draw can be rejected, suppressing the CI failure alert entirely; apply the same ASCII sanitization used by the calendar integration before constructing the element.
Useful? React with 👍 / 👎.
| <key>EnvironmentVariables</key> | ||
| <dict> | ||
| <key>PYTHONPATH</key> | ||
| <string>__REPO__/src</string> |
There was a problem hiding this comment.
Add the GitHub CLI directory to the LaunchAgent PATH
When this plist is installed as documented and gh comes from Homebrew (commonly /opt/homebrew/bin/gh), launchd does not inherit the user's interactive-shell PATH. Although uv is invoked through an absolute placeholder, get_token() launches gh by name, so the agent reports that GitHub CLI is missing and KeepAlive repeatedly restarts it. Populate PATH with the resolved gh directory or substitute an absolute gh path during installation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c08c782c22
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if runs is not None: # None = 304/no-change/error -> keep cached state | ||
| state_cache[repo] = evaluate_runs(repo, runs, now, | ||
| c["stale_queued_minutes"]) | ||
| payload = build_ci_payload(list(state_cache.values()), c["show_green"], timeout_s) |
There was a problem hiding this comment.
Avoid clearing alerts before every repository has a state
When a repository's first request fails (for example, a timeout, 401, or 500), fetch_runs returns None and that repository is absent from state_cache; building the payload from only the cached values can therefore display CI ok or call clear despite the unevaluated repository still having failures. This is especially harmful after a process restart because a transient GitHub error can immediately remove an alert left on the device by the previous process; defer the green/clear decision until every configured repository has a known state.
Useful? React with 👍 / 👎.
| busy = client.get_busy() or {} | ||
| if busy.get("type") in (None, "NOT_STARTED"): | ||
| client.set_busy_simple(remaining_ms) |
There was a problem hiding this comment.
Do not treat a failed BUSY lookup as an inactive session
When get_busy() returns None because its GET timed out or received a non-200 response, or {} makes the subsequent check treat the state as inactive. If the following PUT succeeds, auto_busy can overwrite an existing user BUSY session with the calendar event's timer; only call set_busy_simple after a successful lookup explicitly reports an inactive state.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9c507413c2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for r in runs: # API returns newest first; keep first seen per workflow | ||
| latest.setdefault(r["workflow_id"], r) |
There was a problem hiding this comment.
Keep failures visible until a rerun completes successfully
When a failed workflow is rerun, the newest run is commonly queued or in_progress with no conclusion, so keeping only that entry causes the prior completed failure to disappear immediately even though nothing has gone green. The alert is then cleared for the entire rerun and remains clear if that run is cancelled; retain the last failing result until a newer run completes successfully.
Useful? React with 👍 / 👎.
| if "ETag" in resp.headers: | ||
| self._etags[repo] = resp.headers["ETag"] | ||
| try: | ||
| return resp.json().get("workflow_runs", []) |
There was a problem hiding this comment.
Cache the ETag only after parsing the response
If a 200 response carries an ETag but its body is malformed, this stores the ETag before resp.json() fails. Subsequent polls can therefore receive 304 for the unprocessable representation and indefinitely retain an old or missing repository state until GitHub changes the resource; parse and validate the workflow-runs payload before committing its ETag.
Useful? React with 👍 / 👎.
Summary
busybarPython package: config loader (deep-merged defaults,BUSYBAR_HOSToverride) and device HTTP client (draw/clear/status/BUSY snapshot) with a four-state draw result (drawn / rejected-409 / http-error / unreachable; only unreachable triggers loop backoff)calendar_countdown: next-meeting countdown from macOS EventKit (macOS 14+), warning color near start, optional auto-BUSY during events, priority 20ci_status: GitHub Actions status via REST only (GraphQL never touched) with per-repo ETag conditional requests (304s cost no quota), red failure alerts at priority 60 with LED fallback during BUSY sessions (priority 90), optional stuck-queued detection for self-hosted runnersPYTHONPATHhardening) + per-integration READMEs + root READMEDesign docs
docs/superpowers/specs/2026-08-03-calendar-ci-integrations-design.mddocs/superpowers/plans/2026-08-03-busybar-integrations-v1.mdTest plan
uv run pytest— 36/36 (any timezone)ci_status --once --dry-runagainst a real repo (correct green-state payload)🤖 Generated with Claude Code