Skip to content

Ceiling 1.5.31

Choose a tag to compare

@github-actions github-actions released this 14 Aug 12:35
· 117 commits to main since this release
ecd098e

[Ceiling] 1.5.31 - 2026-08-14

Hardens updates, credentials, and the local serve API, and ships the unpublished 1.5.30 work: a second tray click hides the dashboard, Grok banked resets show, and the taskbar strip finds a second lane when the usual gap is gone.

Supersedes 1.5.30. The GitHub release was tagged and drafted on 2026-08-13 but never published; the v1.5.30 tag stays as a marker. A Microsoft Store submission was created from that tag. Everything from it is included here.

Security

  • Ceiling checks who signed an update before it runs it. An automatic update was trusted on the strength of the SHA256 that GitHub's release metadata reported, so that metadata was the only thing standing between Ceiling and launching an attacker's installer with the user's privileges. Every downloaded installer is now independently checked against Windows Authenticode and pinned to Ceiling's publisher identity, once when the download completes and again immediately before launch. An installer that fails either check is deleted instead of being left on disk to be retried. The publisher identity is pinned rather than the signing key, because Azure Trusted Signing issues a fresh short-lived leaf certificate for every release and a key pin would reject the next legitimate one.
  • codexbar serve now requires a per-user bearer token. The local HTTP API bound to loopback with only a Host check, so any process on the machine could read usage, email, organization, and raw provider errors. /usage and /cost now need Authorization: Bearer unless you pass --allow-unauthenticated. The token is created at <config>/Ceiling/serve.token with current-user ACL on Windows and mode 0600 elsewhere, and is printed only the first time. Identity and raw provider errors are omitted by default; --include-identity opts back in. /health stays open.
  • Windows-owned PowerShell and where.exe no longer launch from PATH. Notifications, Antigravity, the updater, and a few where lookups used a bare name, so a hostile current directory or PATH entry could substitute the binary. They now resolve under %SystemRoot%\System32 and refuse to start if that file is missing. Claude, Codex, and gh still use PATH.
  • Cookie and token paste fields are masked, and destructive credential actions confirm first. Manual cookies and token accounts used raw textareas, and Remove/Revoke fired on a single click. Secrets are hidden by default with an explicit reveal, and cookie, API-key, token-account, and provider-wide revoke go through an in-app dialog that names the provider and credential type. A successful removal refreshes provider state so leftover quota or identity is not left on screen.

Added

  • A second click on the tray icon hides the dashboard. Left-click always reopened the stats window, so a glance meant hunting for the close button. First click shows it, second click hides it. A Windows double-click is ignored so the window does not flash open and disappear. Right-click and the keyboard shortcut are unchanged. Closes #280.
  • Grok banked resets show up the way Codex already did. Grok's Settings → Usage now lists redeemable reset tokens. Ceiling already counted those for Codex and left Grok's chip blank. The same count now appears on Overview, provider detail, and the taskbar flyout. Redeeming still happens on grok.com. Ceiling only displays how many are left.
  • Settings says why the taskbar widget is hidden. The native strip can vanish because there is no gap, because Start cannot be found, or because no providers are enabled, and none of that was visible anywhere, so the feature looked broken. The Taskbar Usage group now reports "Shown on N taskbar(s)", no free space, waiting for landmarks, or no enabled providers. The row stays off when the widget is disabled. Thanks @diogochaves!
  • OpenCode and OpenCode Go show their mark on the taskbar strip. Those providers had no bitmap in the native overlay, so the tile was a blank disc. They now use the square-ring glyph and the same blue as the rest of the app.

Fixed

  • diagnose no longer copies provider error text into an export meant to be shared. The command already withheld cookies, tokens, account emails, and response bodies, but a failed fetch put the provider's own error string straight into the payload, and that string can carry a signed URL, an account identifier, or an echoed request header. Every failure now reports a local category and a fixed local message instead of anything the provider said.
  • Heavy Codex sessions no longer vanish from Charts and cost totals. Codex usage was parsed into 32-bit integers, so a cumulative total past roughly 2.1 billion tokens wrapped to a negative number, which the next step clamped to zero. The effect was not a wrong number but a missing one: the session's tokens and its dollars simply stopped being counted. Token counts are now 64-bit from the log line through to the summary, cumulative deltas saturate instead of wrapping, a genuinely corrupt total is skipped without poisoning the running baseline, and a decreasing cumulative total no longer lowers that baseline so the next valid line over-counts.
  • Adding, removing, or switching an account no longer races another change. Every account edit read the store, changed it in memory, and wrote it back, with nothing holding those three steps together. Two edits that overlapped could each write a copy of the file that predated the other, so one of them disappeared with no error and no sign anything had gone wrong. A Copilot device login finishing while the user edited accounts in Settings could do the same. Account changes now run as a single locked transaction against the latest state on disk.
  • A token refresh no longer rewrites the whole Gemini or Grok credential file. Both providers refreshed by reading the file, rebuilding it from the fields Ceiling models, and writing it back with no lock and no atomic replace. Three things could go wrong: two refreshes at once could interleave and lose one, a crash partway through the write could truncate the file and sign the user out, and any field Ceiling did not model was dropped on the floor, including a newer refresh token the provider's own CLI had just written. A refresh now holds a lock for the whole read-modify-write, merges only the fields that actually changed, and replaces the file atomically while preserving its original permissions. Grok writes refresh_token only when the token endpoint issued a new one. If the credential file is missing, persist recreates it instead of failing the refresh.
  • Charts no longer mix one account's usage into another. A removed or unresolved account could inherit the machine-wide transcript cache, so Charts showed someone else's tokens under the wrong tab. Each configured account now has its own chart tab and its own cache identity. Compare stays an explicit aggregate. Polling stops for identities that no longer resolve.
  • Provider detail no longer applies a stale refresh. Switching providers or accounts quickly could let an older request finish last and paint the previous seat's numbers onto the new one. Detail commits now require a matching request epoch plus provider and account identity, account selection resets at provider boundaries, and bursty provider updates coalesce before a refresh.
  • Credential status is scoped to the provider you have selected. Shared credential-file protection was treated as "this provider has a credential", so opening provider B could show a protected store and a revoke action that belonged to provider A. Presence is now per selected provider, and per selected token account. An unreadable store stays unknown instead of reporting a false absence.
  • The taskbar widget takes a second lane when the usual one does not fit. On a left-aligned taskbar, Start sits at the left edge and the Widgets-to-Start gap's right edge goes negative, so the strip hid with no explanation. Windhawk's "Start button always on left" did the same. The Widgets-to-Start lane is still preferred. If it has no verified gap, the widget tries the stretch between Start and the tray and lands in the largest empty gap, never covering a pinned icon. Left alignment with Windows Widgets enabled used to freeze the strip on top of the app icons instead. That path now places into the fallback lane and treats the Widgets entry as an obstacle. Closes #261. Thanks @diogochaves!

Internal

  • The privileged PR-review runner pins actions/checkout to a reviewed commit instead of a moving tag.

What's Changed

  • Credit the contributor in the 1.5.29 changelog entry by @tsouth89 in #265
  • Pin checkout on the persistent PR review runner by @tsouth89 in #276
  • Surface the taskbar widget's hidden state in Settings by @diogochaves in #264
  • Scope credential status to the selected provider by @tsouth89 in #279
  • Show Grok banked usage resets by @tsouth89 in #282
  • Isolate chart data by account by @tsouth89 in #277
  • Fall back to a second taskbar lane when the Widgets→Start lane can't fit the widget by @diogochaves in #281
  • Discard stale provider detail responses by @tsouth89 in #278
  • Hide the dashboard on a second tray icon click by @tsouth89 in #283
  • Prepare Ceiling 1.5.30 by @tsouth89 in #284
  • Keep provider error text out of the diagnose export by @tsouth89 in #286
  • Count Codex tokens beyond the 32-bit ceiling by @tsouth89 in #288
  • Make account store edits transactional by @tsouth89 in #289
  • Verify the Authenticode signature of every downloaded update by @tsouth89 in #285
  • Merge credential refreshes instead of rewriting the file by @tsouth89 in #287
  • Finish leftover Aug 12 security follow-ups by @tsouth89 in #291
  • Draw the OpenCode mark on the taskbar strip by @tsouth89 in #290
  • Prepare Ceiling 1.5.31 by @tsouth89 in #292

New Contributors

Full Changelog: v1.5.29...v1.5.31