Skip to content

feat(hub): a Locks tab that says what it does not know - #57

Merged
drknowhow merged 1 commit into
mainfrom
feat/hub-locks-tab
Jul 30, 2026
Merged

feat(hub): a Locks tab that says what it does not know#57
drknowhow merged 1 commit into
mainfrom
feat/hub-locks-tab

Conversation

@drknowhow

Copy link
Copy Markdown
Owner

Completes P4 of docs/agent-locks.md. The FleetDeck reader landed in #56; this is the Hub UI half.

Why a separate PR: this commit was pushed to feat/agent-locks-p3-p4 after #56 had already merged, so it was stranded — GitHub never fired a synchronize event, no CI run was created for it, and gh pr checks kept reporting #56's older green as if it covered this code. Cherry-picked onto the updated main instead.

What it is

A cross-project lease view plus the one human override: force-release, which bumps the fencing counter so a holder that comes back is stale by construction. That deliberately stays out of the agent-facing c3_locks tool — breaking someone else's lease is a person's decision.

  • cli/hub_ui/components/hub_locks.js — the view
  • GET /api/hub/locks/overview — cross-project snapshot, per-row isolated
  • POST /api/projects/locks/force-release — audited on the target project

Two honesty rules the view keeps

Both from §4.4 and §9 of the spec:

  • A project whose lock state could not be READ is badged UNREADABLE, not rendered with zero leases. "All clear" is a different claim from "we don't know", and only the second is supportable from an unreadable file. Same reasoning as the FleetDeck reader's available: false.
  • The coverage caveat stays on screen. A lease gates C3 tool surfaces only — a raw shell redirect, a non-Claude agent, or a human in an editor is not covered. The view should never imply more protection than exists.

Projects that cannot coordinate at all (no .c3/, locks disabled, unreadable state) are listed explicitly under "Not coordinating" rather than silently omitted, because silence there reads as all-clear.

Verified in a browser, not only by test

The tab renders live leases with holder, intent, and a draining TTL bar. A full force-release round trip:

lease removed          2 → 1
fencing bumped         2 → 3     (old holder now stale by construction)
ledger entry written   lock://cli/hub_ui/components/hub_locks.js
                       prev_owner=claude-code:demo1234

Two traps hit on the way, both worth knowing:

  • Stale server. Port 3330 was held by a hub started hours earlier still serving 2.63.0, so the new route 404'd and looked missing. Restarted from the branch before believing anything.
  • Screenshot pixels ≠ CSS pixels. The first force-release click hit nothing — I used coordinates read off a screenshot, which is image space (~1.25× here). Querying the element's real position fixed it.

Tests

tests/test_hub_locks_routes.py — 11 tests. The two that matter most pin the honesty rules: an uninitialized project is flagged rather than reported empty, and force-release must bump fencing (without it a returning holder still looks current and the override achieved nothing). Also asserts hub_locks.js is in the served bundle list, because a component missing from it renders as a blank tab with no error — the failure mode is silence.

1805 pass, ruff clean. The same commit was verified green on all 11 CI jobs via workflow_dispatch (run 30580000933) before this PR existed.

https://claude.ai/code/session_01Sbd9NbQfDcoJvvURD7KCVf

Completes P4. Cross-project lease view plus the one human override: force-release, which bumps the fencing counter so a holder that comes back is stale by construction. That stays out of the agent-facing c3_locks tool on purpose -- breaking someone else's lease is a decision for a person.

Two honesty rules the view keeps, both from the spec. A project whose lock state could not be READ is badged UNREADABLE rather than rendered with zero leases: 'all clear' is a different claim from 'we don't know', and the second one is what an unreadable repo actually supports. And the coverage caveat stays on screen, because a lease gates C3 tool surfaces only -- a raw shell redirect, a non-Claude agent, or a human in an editor is not covered.

Verified in a real browser rather than by unit test alone: the tab renders live leases with holder, intent and a draining TTL bar, and a force-release round trip removed the lease, moved fencing 2 to 3, and wrote a ledger entry naming the previous owner. Worth recording that the first attempt clicked nothing -- I used coordinates read off a screenshot, which is image space, not CSS space. Querying the element position fixed it.

Also caught the stale-server trap on the way: port 3330 was held by a hub started hours earlier still serving 2.63.0, so the new route 404'd and looked missing. Restarted from the branch before believing anything.

1805 pass, ruff clean.

Claude-Session: https://claude.ai/code/session_01Sbd9NbQfDcoJvvURD7KCVf
@drknowhow
drknowhow merged commit 2c6c76c into main Jul 30, 2026
11 checks passed
@drknowhow
drknowhow deleted the feat/hub-locks-tab branch July 30, 2026 20:50
drknowhow added a commit that referenced this pull request Jul 30, 2026
…off (#58)

Four merged PRs (#54-#57) had accumulated past the v2.64.0 tag, so the version
no longer identified the code: the installed build reported 2.64.0 while
running leases, a new tool, a new CLI command and a new Hub tab.

MINOR, not patch. I earlier called this 2.64.1; that was wrong. It adds
c3_locks, `c3 locks`, a Hub tab and a config section — new surface, not a fix.

What ships:

- Agent Locks. Two mechanisms for two different problems. A cross-process file
  lock stops two c3-mcp processes tearing each other's writes (and now covers
  create mode, which ran outside the lock entirely). Leases stop two agents
  doing overlapping work, carrying the intent from the edit summary so the
  blocked agent is told who holds the file and why. TTL is the real release
  mechanism, so a crashed agent cannot wedge a repo.
- c3_locks tool, `c3 locks` CLI with the human-only force-release, and a Hub
  Locks tab that badges an unreadable project UNREADABLE rather than showing
  it with zero leases.
- Access Guard built-in opt-out, two-key: a config entry AND a keyring
  attestation. Either alone leaves the built-in enforcing, so an agent that
  writes config.json cannot grant itself write access to ~/.claude/settings.json.
  The credential vault stays absolute.

The changelog states coverage honestly rather than implying it: leases gate
C3's own tool surfaces, and a raw c3_shell redirect, a non-Claude agent, or a
human in an editor is not covered.

1805 pass, ruff clean, both version constants bumped (test_version_sync guards
the pair).

Claude-Session: https://claude.ai/code/session_01Sbd9NbQfDcoJvvURD7KCVf
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