Skip to content

fix(dashboard): atomic, non-fatal statusline writes - #116

Merged
TraderSamwise merged 2 commits into
masterfrom
fix/statusline-atomic-write
Jun 14, 2026
Merged

fix(dashboard): atomic, non-fatal statusline writes#116
TraderSamwise merged 2 commits into
masterfrom
fix/statusline-atomic-write

Conversation

@TraderSamwise

@TraderSamwise TraderSamwise commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Problem

The dashboard could crash on startup with:

aimux: failed to spawn "undefined": ENOENT: no such file or directory,
rename '…/tmux-statusline/bottom-dashboard-…-client-….txt.tmp' → '…txt'
aimux dashboard failed to start.

Two defects:

  1. Racy statusline write. rewriteLocalStatuslineArtifacts (src/main.ts) hand-rolled an "atomic" write using a single shared <file>.tmp path. When more than one client/refresh writes the same statusline file concurrently, they collide on that temp path and one renames the other's temp out from under it → ENOENT on rename. A cosmetic statusline write then took down the whole dashboard.
  2. Misleading error. In dashboard mode tool is undefined, so the top-level catch printed failed to spawn "undefined" for what was really a dashboard-startup error.

Fix

  • Statusline writes now use the existing writeTextAtomic (unique per-write temp name + mkdir -p + fsync) instead of a shared .tmp, and are wrapped in try/catch so a write failure is logged and ignored — statusline is cosmetic tmux chrome and must never abort dashboard startup.
  • The catch reports dashboard failed to start when there is no tool, instead of failed to spawn "undefined".

Test

atomic-write.test.ts: 50 concurrent writers to one path never ENOENT, leave a valid final value, and litter no temp files.

yarn typecheck / lint / build / full vitest (1115) all pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests

    • Added test coverage for atomic file write operations to ensure race-condition safety.
  • Bug Fixes

    • Improved file write reliability by implementing atomic write operations across statusline and artifact updates, preventing partial writes and data corruption in concurrent scenarios.

The dashboard could crash on startup with `ENOENT: rename
…/tmux-statusline/…txt.tmp → …txt` (surfaced confusingly as
`failed to spawn "undefined"` in dashboard mode). Root cause:
rewriteLocalStatuslineArtifacts hand-rolled a write using a single
shared `<file>.tmp` path, so concurrent clients/refreshes raced — one
renamed the other's temp file out from under it.

- statusline writes now go through writeTextAtomic (unique per-write temp
  name + mkdir + fsync) and are wrapped so a write failure logs and is
  ignored rather than aborting dashboard startup — statusline is cosmetic
  tmux chrome and must never take the dashboard down
- the top-level catch no longer reports `failed to spawn "undefined"` when
  there is no tool; dashboard-mode failures now say "dashboard failed to
  start"
- test: 50 concurrent writers to one path never ENOENT, leave a valid
  result, and litter no temp files

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Jun 14, 2026 7:44am

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4e8b54a-4553-49f5-ae9e-207a41c0d20b

📥 Commits

Reviewing files that changed from the base of the PR and between f9b5f7d and 9e73bcc.

📒 Files selected for processing (4)
  • src/atomic-write.test.ts
  • src/main.ts
  • src/multiplexer/dashboard-control.ts
  • src/multiplexer/persistence-methods.ts

📝 Walkthrough

Walkthrough

Three statusline file-writing call sites in main.ts, dashboard-control.ts, and persistence-methods.ts are migrated from manual .tmp + renameSync to the shared writeTextAtomic helper, with write failures caught and logged via debug. A new test in atomic-write.test.ts verifies that writeTextAtomic uses a unique temp path even when a directory exists at the conventional .tmp collision path.

Changes

Atomic write migration for statusline files

Layer / File(s) Summary
Migrate statusline writers to writeTextAtomic
src/multiplexer/persistence-methods.ts, src/multiplexer/dashboard-control.ts, src/main.ts
Removes renameSync/writeFileSync direct imports; adds writeTextAtomic/writeJsonAtomic and debug imports. All three statusline write implementations (writeStatuslineFile, writeStatuslineTextFile, rewriteLocalStatuslineArtifacts) now delegate to writeTextAtomic inside a try/catch that swallows errors and logs via debug. Also updates the CLI top-level error message to differentiate tool-spawn failures from dashboard startup failures.
Race-collision test for unique temp paths
src/atomic-write.test.ts
Adds mkdirSync to the test import; adds a new test that pre-creates a directory at ${target}.tmp to simulate a path collision, then asserts the write completes successfully, the target content is correct, and no .tmp files remain.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • TraderSamwise/aimux#99: Introduced the writeTextAtomic helper and its atomic temp-file semantics that this PR adopts across all statusline write sites.

Poem

🐇 No more shared .tmp in the warren's hall,
Each write gets its own burrow, safe from all,
Renaming by hand was a rickety gate—
Now writeTextAtomic keeps files straight.
No collisions, no races, no leftover mess,
Just atomic hops forward, nothing less! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and concisely summarizes the main change: converting statusline writes to atomic operations with non-fatal error handling. It is clear, specific, and captures the primary objectives of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/statusline-atomic-write

Comment @coderabbitai help to get the list of available commands and usage tips.

Follow-up to the same race: the daemon/dashboard runtime had two more
hand-rolled shared-".tmp" statusline writers (the ones that actually
produced the observed bottom-dashboard-<client>.txt crash), plus the
statusline.json snapshot write:

- dashboard-control.ts writeStatuslineTextFile (primeLiveTmuxFooter)
- persistence-methods.ts writeStatuslineTextFile (precompute path)
- persistence-methods.ts statusline.json snapshot

All now go through writeTextAtomic (unique temp + mkdir + fsync); the
text writers are wrapped so a failure logs and is ignored rather than
crashing. statusline.json keeps its compact JSON via writeTextAtomic
(not the pretty writeJsonAtomic).

Test reworked to deterministically prove the unique-temp property:
occupying the shared "<file>.tmp" path as a directory fails the old
fixed-".tmp" writer but not the unique-temp one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@TraderSamwise
TraderSamwise merged commit f57dc50 into master Jun 14, 2026
3 checks passed
@TraderSamwise
TraderSamwise deleted the fix/statusline-atomic-write branch June 14, 2026 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant