cctally 1.88.0 → 1.88.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [1.88.1] - 2026-07-31
9
+
10
+ ### Fixed
11
+ - Session names no longer disappear from the Recent Sessions card while cctally is pruning old transcripts. The prune held the transcript store's maintenance lock exclusively for its entire pass, and the Sessions card's name lookup is deliberately built to give up instantly rather than wait on that lock, so every Claude session rendered a blank name until the prune finished — minutes on a large store, and for most installs on the v1.88.0 upgrade in particular, whose one-off transcript re-read is followed by an unthrottled sweep. The prune still claims the lock exclusively, which is what keeps it serialized against a second prune or a `cctally db vacuum`, but now holds it shared for the pass itself so read-only lookups are no longer locked out. What gets pruned, and when, is unchanged.
12
+
13
+ ### Security
14
+ - Update the build-time `postcss` dependency to 8.5.25, clearing a path-traversal advisory (GHSA-r28c-9q8g-f849) in its source-map auto-loading. It is a development dependency used only when building the dashboard bundle from source, so it is absent from npm, Homebrew, and every other install, and the shipped dashboard is byte-for-byte unchanged.
15
+
8
16
  ## [1.88.0] - 2026-07-31
9
17
 
10
18
  ### Fixed
@@ -255,7 +255,11 @@ def _maybe_prune_conversation_retention(
255
255
  skipped (retention disabled, throttled within 24h, or a lock contended).
256
256
 
257
257
  Concurrency (F7): a dedicated non-blocking MAINTENANCE flock serializes prune
258
- attempts across processes (a second dashboard skips cleanly). Under it, the
258
+ attempts across processes (a second dashboard skips cleanly). It is claimed
259
+ EXCLUSIVE and then downgraded to SHARED for the pass proper, so a long prune
260
+ cannot starve the fail-closed panel readers that sample it
261
+ ``LOCK_SH | LOCK_NB``; a rival ``LOCK_EX | LOCK_NB`` claim still fails
262
+ against the held SHARED, so the serialization is unchanged. Under it, the
259
263
  two provider flocks are taken in a FIXED order (Claude then Codex),
260
264
  non-blocking, so a rebuild/reingest mid-flight makes the prune skip this
261
265
  cycle rather than race between candidate selection and deletion. The prune of
@@ -297,6 +301,26 @@ def _maybe_prune_conversation_retention(
297
301
  fcntl.flock(codex_fh, fcntl.LOCK_EX | fcntl.LOCK_NB)
298
302
  except (BlockingIOError, OSError):
299
303
  return None # a Codex sync is mid-flight; retry next cycle
304
+ # Downgrade the maintenance flock to SHARED for the pass proper.
305
+ # The EXCLUSIVE acquire above is what wins the race; holding it
306
+ # exclusive for the whole pass additionally locked out the
307
+ # fail-CLOSED panel readers, which take this flock
308
+ # `LOCK_SH | LOCK_NB` and blank their column on any contention
309
+ # (`read_session_titles_bounded` -> every Claude session title in
310
+ # the Recent Sessions card, for the minutes a large prune runs).
311
+ # A prune is a writer, not a family replacement: concurrent
312
+ # writes are already excluded by the two provider flocks, and
313
+ # the replacement paths readers guard against take this flock
314
+ # EXCLUSIVE, which a held SHARED still blocks. Rival prunes and
315
+ # `db vacuum` claim `LOCK_EX | LOCK_NB`, which also still fails.
316
+ # MUST stay after both provider flocks: a flock conversion is
317
+ # not atomic, so a rival can slip into the downgrade window —
318
+ # the provider flocks it then fails to claim are what make that
319
+ # harmless.
320
+ try:
321
+ fcntl.flock(maint_fh, fcntl.LOCK_SH)
322
+ except OSError:
323
+ pass # keep the exclusive hold; correctness is unchanged
300
324
  cutoff = now_utc - dt.timedelta(days=int(retention_days))
301
325
  conn.execute("BEGIN IMMEDIATE")
302
326
  try:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cctally",
3
- "version": "1.88.0",
3
+ "version": "1.88.1",
4
4
  "description": "Claude Code usage tracker and local dashboard for Pro/Max subscription limits - weekly cost-per-percent trend, quota forecasts, threshold alerts. ccusage-compatible.",
5
5
  "homepage": "https://github.com/omrikais/cctally",
6
6
  "repository": {