fix(fleet): preserve pool identity and improve seat diagnostics - #289
fix(fleet): preserve pool identity and improve seat diagnostics#289itsHabib wants to merge 10 commits into
Conversation
DETACHED_PROCESS on a console-subsystem binary gives the child its own console, which is a window on screen. Since any session revives a watcher it believes is stale, one flashes per SessionStart, and a machine running several sessions gets a stream of them. CREATE_NO_WINDOW is the flag that means "console application, no window". It is documented as invalid combined with DETACHED_PROCESS, so this replaces that flag rather than adding to it; CREATE_NEW_PROCESS_GROUP still keeps the watcher out of the parent's Ctrl-C group. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…assign hid the useful git error Two fixes in the seat path, both found standing a pool up on a machine that already had roles.map lines. pool took the label in a seat's role from filepath.Base(checkout). A directory basename is not a repo identity: a checkout at ~/dev/Mono produced <kind>:Mono beside an existing <kind>:mono, so `fleet work --for <kind>:mono` matched none of the new seats and the board showed two families for one repo. There was no way to ask for the other spelling either, since pool takes --tenant but no label. It now inherits the label from the checkout's own map line, exactly as poolTenant already inherits the tenant, and falls back to the basename only when there is no line. This also removes the reason a hand-fix would not stick. pool re-roles the seats it KEEPS, not only the ones it creates, so deriving the label per run overwrote a corrected value on every top-up. assign tries `checkout <branch>` then `checkout -b <branch> origin/<branch>`, and reported the second failure. The fallback exists for a branch that is not local yet, so its message can only say the branch already exists, which is what the first attempt just established. The first attempt's message names the worktree holding the branch, which is the whole diagnostic, and this is the common case when a seat takes over an existing branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ares writeDenies unions the seat's existing denies with the manifest's, so a deny can only ever be added. Dropping one from a lane manifest is a no-op against seats already roled, and a seat's permissions become a high-water mark of every deny that lane ever declared. The merge itself is deliberate and stays: it preserves denies a person added to settings.local.json by hand. What was missing is any signal that a narrowing did not take. Roling two seats of one kind in a single pool run, from one manifest, produced two seats enforcing different rules and said nothing. writeDenies now also returns the denies present in the file that the manifest does not declare, and cmdRole names them and points at the file to edit. Nothing about what gets written changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rollback picked `ls -1t "$f".bak-* | head -1`. The ordering is right, the glob is not: it also matches an unrelated sibling someone left beside the file, and restoring one over live harness config silently drops whatever else that snapshot did not contain. This happened with a settings.json.bak-env, which cost three env vars before it was noticed. Constrain the glob to the timestamp shape the script itself writes. A restore target should be a file the tool can prove it created. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Leaving a main checkout unroled is the right call once people open ad-hoc sessions in it: otherwise every such session boots wearing a lane's card, and for an exclusive lane that means two sessions on one role. But both the tenant and the repo label were inherited from that single line, so removing it broke pool two ways. poolTenant refused for want of a tenant. poolLabel fell back to the directory basename, and since pool re-roles the seats it KEEPS, that silently re-labelled every existing seat of the repo. Add sameRepoRow: a sibling seat of the same repo, matched on RepoID, knows both answers. Consulted after the checkout's own line and before ORG_TENANT, so explicit configuration still wins and nothing about the roled case changes. Verified against a real pool: with no line for the checkout at all, `fleet pool` inherits tenant and label from a sibling seat, keeps six seats, and leaves every label intact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…gnment named a role `fleet assign --for <role>` records the accountable role, the dispatcher, the seat and the brief. undeclaredRows read none of it and hardcoded them nil, so the board printed "for no one accountable" about a change whose own assignment record named the role. Two records fleet wrote, disagreeing, and the operator reasonably read the board as the truth. The STATE stays `undeclared` — that was right, a seat assignment is not an ownership declaration — but the columns now come from the assignment when there is one. A branch nobody assigned still reads "no one accountable", so the change is precise rather than blanket. Also rename undeliveredAssigns to assignsByChange. Its comment claimed "every assignment not yet read by a session", but nothing in it filters `delivered_to`; that filter lives in the board's row builder. Reusing it under the old name would have looked like it dropped delivered assignments when it does not. Verified on a live board: two assigned seats now group under author:nx-apps and liverun:sidebar and name their seats, while an unassigned branch still reports no one accountable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@codex review |
|
@claude review |
|
Claude finished @itsHabib's task in 3m 16s —— View job PR Review — fix(fleet): four seat-path fixes
Five distinct fixes (the PR body says four, but I count the Fix 1 — Windows console window (
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Reviewed source head Found and fixed: sibling inheritance chose the first row even when tenants or labels disagreed, and label selection could cross the explicitly selected tenant. A real-Git regression failed on the source head ( Added missing regression coverage around pool identity, original assignment diagnostics, retained denies and rollback backup selection, plus a macOS/Windows portability job and Windows process-flag test. Mac Fleet race tests, whole-module vet/lint and Windows cross-compilation passed. Required GitHub reviews have been requested for the pushed head; this comment is not their completion or merge authorization. The original PR body claimed extra-deny unit coverage but contained no added test file; that coverage now exists. The body/title now describe the complete change and distinguish Windows field observation, Mac tests, cross-builds and remaining live Windows acceptance. Separate next work: observable/effective hook wiring, then bounded native migration preserving env prefixes and unrelated handlers. The day recap argues against making more dispatch machinery a prerequisite for tomorrow. Handoff: https://github.com/itsHabib/cc-skills/blob/fleet-policy-boundary/docs/features/fleet-policy-boundary/workday-handoff.md |
|
Reviewer: claude |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 925b785a80
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| repo, branch := fleet.S(parts, "repo"), fleet.S(parts, "branch") | ||
| row := WorkRow{"change": branch, "repo": repo, "relationship": nil, "for": nil, "by": nil, | ||
| "at": l["at"], "due": nil, "slot": nil, "brief": nil, "key": key, "hands": sid, "state": state, "head": nil, "done_at": nil} | ||
| if a := assigns[[2]string{repo, branch}]; a != nil { |
There was a problem hiding this comment.
Match assignment metadata to the holding slot
When a branch is assigned successively to multiple seats after the original seat switches away, both assignment files remain. assignsByChange collapses them by (repo, branch) according to slot-filename iteration order, so this lookup can attach the older seat's for, slot, and brief to the current lease holder. Consequently fleet work and watcher output may name the wrong accountable role; select the assignment associated with the holder's session/slot, or otherwise resolve multiple records by a defined recency rule.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 077fcd9. Regression reproduced lead:old/z-old on the current holder. The board now reads the holder session’s slot record and validates repo, branch, slot and delivery recipient; context delivered to another session is withheld. This regression is included in both portability jobs.
There was a problem hiding this comment.
🟡 Changes recommended
poolTenant can still bypass the sibling-ambiguity check by accepting a longest-prefix inherited tenant, which can allow ambiguous same-repo tenant bindings to slip through and be rewritten during pool top-ups.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR hardens cmd/fleet seat lifecycle behavior based on real-world pool stand-up friction: fixing Windows watcher detachment behavior, making pool identity (tenant/label) derive from authoritative roles.map bindings, improving assign diagnostics, surfacing “extra denies” that remain after a manifest narrows, and tightening install.sh --rollback to only restore backups it created.
Changes:
- Pool identity: detect ambiguous sibling tenants/labels via repo-common-dir identity and refuse before creating/re-roling seats; add regression tests and a portability workflow.
- Assignment + board: show assignment columns for undeclared rows; ensure
assignreports the diagnostically useful checkout error. - Operational fixes: Windows watcher spawn uses
CREATE_NO_WINDOW; rollback backup matching constrained to timestamp-shaped backups; extra-denies are reported to the user.
File summaries
| File | Description |
|---|---|
| friction-log.md | Logs the real-world pool identity friction and the validated fix. |
| cmd/fleet/internal/watch/detach_windows.go | Switches Windows watcher process creation flags to avoid visible console windows. |
| cmd/fleet/internal/watch/detach_windows_test.go | Adds a Windows-only unit test asserting expected process creation flags. |
| cmd/fleet/internal/verbs/work.go | Populates “undeclared” board rows from assignment records when present. |
| cmd/fleet/internal/verbs/views.go | Implements repo-identity-based tenant/label inheritance + ambiguity refusal; improves assignCheckout error reporting; renames assignment indexer. |
| cmd/fleet/internal/verbs/role.go | Enhances deny merge to report “extras kept” when a manifest narrows denies. |
| cmd/fleet/internal/verbs/pool_identity_test.go | Adds real-git regression tests for tenant/label ambiguity, deny extras, and assign error messaging. |
| cmd/fleet/install.sh | Tightens rollback backup selection to backups written by this script (timestamp-shaped). |
| cmd/fleet/install_test.go | Adds a regression test ensuring rollback ignores unrelated .bak-* siblings. |
| .github/workflows/fleet-portability.yml | Adds CI coverage for portability-sensitive fleet tests on macOS + Windows. |
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
@codex review |
|
@claude review |
|
Claude finished @itsHabib's task in 2m 16s —— View job PR Review — fix(fleet): preserve pool identity and improve seat diagnostics
Reviewing at HEAD Commit
|
| Area | Verdict |
|---|---|
Windows CREATE_NO_WINDOW / process group flags |
Correct; portability CI covers it |
| Pool label from roles.map, not directory name | Correct; poolLabel resolution order is well-reasoned |
assignCheckout surfaces the right git error |
Correct; fallback diagnostic deliberately discarded |
writeDenies reports extras |
Correct; merge semantics unchanged, signal added |
install.sh --rollback glob anchored to timestamp shape |
Correct; shell test is the right structure |
Undeclared row enrichment + assignsByChange rename |
Correct; rename fixes a misleading promise in the old name |
holderAssignment replaces map-collapse |
Correct; fixes stale-seat metadata on board |
poolTenant ancestor does not hide sibling ambiguity |
Correct; exact binding before sibling, ancestor last resort |
All fixes are minimal, diagnosed by regression, and backed by tests that would have caught the originals. The friction-log entries (two new entries) document the class of failure clearly.
No correctness issues. Ready to merge.
|
Reviewer: claude |
|
@codex review |
|
@claude review |
There was a problem hiding this comment.
🔵 Needs a closer look
writeDenies can report duplicate “extra denies” when the existing settings file contains duplicate deny entries, making the new diagnostic note misleading.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
cmd/fleet/internal/verbs/role.go:458
extracan contain duplicate deny entries if the existing settings file lists the same deny more than once (fleet.Strs preserves duplicates). That makes the NOTE message’s count/list misleading even though the merge output is deduped via denySet.
- Files reviewed: 11/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
Claude finished @itsHabib's task in 2m 2s —— View job PR Review — fix(fleet): preserve pool identity and improve seat diagnostics
Reviewing at HEAD Commit
|
|
Reviewer: claude |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Mac continuation — validation and handoff
Fixed during this continuation: ambiguous sibling tenant/label inference (including ancestor-binding bypass), cross-tenant label selection, and old-seat accountability appearing against a current lease holder. Each behavioral finding has a regression that failed before its fix. The Windows job also caught a test-only short-path spelling mismatch; the portable assertion now passes on Windows. Tomorrow's handoff is published and reviewed on cc-skills #62, final docs head Start with the continuing session and bounded spin-offs that the recap says worked. Preserve nmpool source on the work machine, inspect actual hook configuration/state roots, and add coordination only for real parallel work or shared resources. Native quoted/backslash hook migration remains separate unfinished work. No live binary was installed and nothing was merged by this Mac continuation. |
Summary
Pool creation and reuse now preserve an existing repository label, refuse ambiguous sibling tenant/label inheritance before creating seats, and keep label lookup within the selected tenant. This fixes a real work-machine case where
Monoseats appeared beside an existingmonorole family and later top-ups undid manual corrections.The change also preserves useful assignment errors, reports retained seat denies, shows recorded accountability for undeclared work, narrows rollback backup selection, and changes Windows watcher process flags.
Behavior
Validation
go test -race -count=1 ./cmd/fleet/...,go vet ./...,golangci-lint run ./...passed; formatter and shell syntax checks passed.Still requires a human Windows check that no console window appears on a real SessionStart. This PR does not repair the quoted/backslash hook migration problem reported on cc-skills #61 and does not prove effective desktop hook wiring. No live homes or installed Fleet binary were changed by the Mac review.