Skip to content

feat(fleet): find work that is stranded, not work that is merely dirty - #39

Merged
github-actions[bot] merged 5 commits into
masterfrom
chore/zellij-drop-hardcoded-tabs
Aug 25, 2026
Merged

feat(fleet): find work that is stranded, not work that is merely dirty#39
github-actions[bot] merged 5 commits into
masterfrom
chore/zellij-drop-hardcoded-tabs

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

Five commits from a day of fleet work. The through-line: a check that cannot go red is not a check, and two of these existed in that state.

The zellij tabs (the original ask)

Every terminal opened with nine hardcoded project tabs, two pointing at directories that no longer exist. projects.kdl's default_tab_template was byte-for-byte identical to zellij setup --dump-layout default, so the file contributed nothing except the stale list — deleted, and .bashrc now runs bare zellij, inheriting cwd from the launching terminal. Zero hardcoded paths.

fleet-stranded — the guard

git-health already reported "118 dirty" every day for ten days and changed nothing, because dirty is not a defect. Every tree is dirty while someone works in it; a number equally true at minute five and day ten reads as noise and then isn't read at all.

Age is the signal. Dirty for an hour is work; dirty for a week is stranded. So this reports only what has aged past a threshold and says nothing when the fleet is healthy — silence-when-clean is the mechanism, not politeness. It also can't be a CI job: the work it looks for has never left the machine, so GitHub cannot see it by definition.

At the time it went in, the fleet held 206 finished files uncommitted for ten days, and fleetcrown had been red on committed code since 14 Aug.

The bug in the guard, found hours later

It scanned $FLEET_ROOT/*/ only — so it never looked inside .claude/worktrees/, which is where every agent session actually works. Fleet-wide that hid:

26 unpushed commits across 25 locations, oldest 25 days

...while the guard printed "clean". That is the exact failure mode named in its own header — read as "all clear" when it means "I did not look there" — built straight into the thing meant to prevent it. A blind spot shaped like the dominant workflow is worse than no guard at all.

Worktrees now report as repo/worktree-dir with their own branch, and the main checkout is not double-counted as one of its own worktrees.

Noise removal

.claude/worktrees/ was untracked in 18 of 25 repos, leaving every tree permanently dirty — precisely the noise that lets real stranded work hide. .playwright-mcp/ went into the global ignore rather than 18 .gitignores: no contributor without this setup ever sees that path, so it was never the projects' business, and one line covers repos that don't exist yet.

Tests

20, in CI. Both directions are pinned, because both are failure modes: it must go red on aged work, and it must stay silent on a fresh dirty tree — the second is the likelier death, since a check that fires on healthy work gets muted, and a muted check is absent. Writing them first caught a real bug in the rename parsing (moved.txt was being read as ed.txt). The three worktree tests all go red with the scan removed.

catomean and others added 5 commits August 24, 2026 15:34
Every login ran `zellij -l projects`, which loaded a hand-maintained
layout naming nine project tabs. That list is a copy of state that
already lives in ~/dev, so it drifted: two of its nine tabs pointed at
directories that no longer exist (openclaw, cockpit), and none pointed
at the repos actually being worked on. Startup reliably produced tabs
nobody wanted.

The layout's only other content was a default_tab_template that is
byte-for-byte zellij's compiled-in default (tab-bar / pane / status-bar,
verified via `zellij setup --dump-layout default`). So the file
contributed nothing but the stale list, and deleting it is the whole
fix — a bare `zellij` yields one tab whose cwd is inherited from the
launching terminal rather than hardcoded anywhere.

Fixes the class, not the instance: there is no longer a project list to
go stale. Open project tabs on demand instead.
This repo already had a fleet health check. It did not prevent the thing it
looks like it prevents: on 2026-08-24 orangecat held 118 uncommitted files
and fleetcrown 88, both untouched for ten days — a finished EntityType rename
and a finished publishing workstream, each existing on exactly one disk.
`git-health` had been reporting them accurately that entire time.

The design error is worth naming, because it is easy to repeat: DIRTY IS NOT
A DEFECT. Every tree is dirty while someone works in it, so a count that is
equally true at minute five and at day ten carries no signal. It gets read as
noise, and then it stops being read. Age is what separates work in progress
from work that is going nowhere.

So stranded-work.sh reports only what has aged past a threshold (default 3d)
and prints NOTHING when the fleet is healthy. The silence is the mechanism,
not manners: a check that speaks on every shell gets muted, and a muted check
is an absent one. It covers both uncommitted files and commits that were made
but never pushed — the second is how four of fleetcrown's commits hid, since
a tree can be clean and still be holding everything back.

It cannot run in CI: the work it looks for has never left the machine, so
GitHub cannot see it by construction. It runs from .bashrc instead, printing
the last scan from cache and refreshing in the background, so it never costs
a prompt. `fleet-stranded` runs it on demand.

The deciding half takes TSV on stdin and touches no git, no filesystem and no
clock, so the tests drive the shipped rule rather than a restatement of it —
the drift the existing ratchet test comments already worry about. Both
failure modes are tested, because either one kills it: it must go red on aged
work, and it must stay silent on a fresh tree.

Writing the tests first paid immediately — the rename case failed, because
the porcelain prefix was being stripped after the "old -> new" arrow was
resolved, which ate three characters of every renamed path. orangecat's
rename touched 18 deletions and a rename; the guard would have misread it.

17 tests, wired into CI.
_claude_autoworktree_enter creates .claude/worktrees/<name> per session, so
every concurrent session leaves a permanently-untracked directory behind. That
is scratch, not source, and it makes `git status` read dirty forever — the
noise that let 206 genuinely stranded files hide across this fleet for ten
days.

Scoped to worktrees/ rather than all of .claude/, so tracked settings and
CLAUDE.md are unaffected. Matches what threadkit already does.
`.playwright-mcp/` is written by the Playwright MCP server whenever an agent
drives a browser. It had appeared in 9 repos and left 2 permanently dirty.

The first fix was a .gitignore line in all 18 — 18 diffs, 18 reviews, and a
19th repo that gets missed next month. But no contributor without this setup
ever sees the path, so it was never the project's business. One line in the
global ignore covers every repo, including ones not created yet.

Matters because a permanently dirty tree is the noise that lets genuinely
stranded work hide — the thing scripts/fleet/stranded-work.sh exists to catch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Shipped this morning to find work that never leaves the machine. It scanned
$FLEET_ROOT/*/ only — so it missed the place this fleet does most of its work.

Every agent session runs in a `git worktree` under .claude/worktrees/, and
those hold real branches. Measured tonight, hours after the guard went in:

    26 unpushed commits across 25 locations, oldest 25 days

...while `fleet-stranded` reported two dirty files in dotfiles and nothing
else. fleetcrown alone held 9 branches, none of them pushed to origin. The
guard was not merely incomplete; it was certifying a fleet that had almost a
month of work sitting on one disk.

That is the failure mode named in this file's own header — a check that gets
read as "all clear" when it means "I did not look there" — and I built it into
the fix for it. A blind spot shaped like the dominant workflow is worse than no
guard at all.

Worktrees now report as `repo/worktree-dir` with their OWN branch, and the main
checkout is not double-counted as one of its own worktrees. Three tests pin it,
including that last one; all three go red with the scan removed.

Found while auditing fleetcrown for code debt — the audit's biggest finding
turned out to be in the auditor.
@github-actions
github-actions Bot merged commit 4c0e923 into master Aug 25, 2026
1 check passed
@github-actions
github-actions Bot deleted the chore/zellij-drop-hardcoded-tabs branch August 25, 2026 20:24
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