Skip to content

fix(fleet): preserve pool identity and improve seat diagnostics - #289

Draft
itsHabib wants to merge 10 commits into
mainfrom
fix/fleet-pool-label-and-narrowing
Draft

fix(fleet): preserve pool identity and improve seat diagnostics#289
itsHabib wants to merge 10 commits into
mainfrom
fix/fleet-pool-label-and-narrowing

Conversation

@itsHabib

@itsHabib itsHabib commented Sep 8, 2026

Copy link
Copy Markdown
Owner

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 Mono seats appeared beside an existing mono role 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

  • Resolve tenant from an explicit argument or exact checkout binding; sibling inference must be unanimous. With no sibling binding, retain ancestor-tenant and environment fallbacks.
  • Resolve labels from the checkout's binding in the selected tenant, then unanimous sibling labels in that tenant, then directory basename. Conflicts refuse before seat creation or roles.map writes. An unroled main checkout can stay unroled.
  • If assignment checkout and its fallback both fail, retain the original Git error naming the checkout already holding the branch.
  • Preserve manually added denies, but print extra denies retained outside the current manifest and the settings file involved.
  • Keep undeclared work in that state while displaying accountability, dispatcher, seat and brief from the current holder’s seat assignment. Validate repo/branch/slot and reject delivery to another session, so old seat assignments cannot misattribute current work.
  • Rollback considers only the timestamp-shaped backup names produced by the installer. This is name filtering, not authenticated backup provenance.
  • Windows watcher uses CREATE_NO_WINDOW plus a new process group, without DETACHED_PROCESS.

Validation

  • Mac: go test -race -count=1 ./cmd/fleet/..., go vet ./..., golangci-lint run ./... passed; formatter and shell syntax checks passed.
  • Real Git regression fixtures: ambiguous tenant refusal (failed on original 0af6100), selected-tenant label filtering, ancestor bindings not hiding conflicting siblings, conflicting-label refusal before mutation, unanimous sibling inheritance, exact binding precedence, original assignment diagnostic, and holder-scoped board context.
  • Regression tests cover retained deny content/notice inputs and rollback ignoring an unrelated backup.
  • Windows Fleet binary and Windows watcher test executable cross-compile on Mac. The portability workflow executes seat and watcher tests on macOS and Windows. CI owns the whole-module suite.
  • Source work agent reports observing label preservation after installation. The Mac session has not independently reproduced that Windows deployment.

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.

itsHabib and others added 7 commits September 8, 2026 11:44
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>
@itsHabib

itsHabib commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@itsHabib

itsHabib commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@itsHabib
itsHabib requested a lite review from Copilot September 9, 2026 02:45
@claude

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown

Claude finished @itsHabib's task in 3m 16s —— View job


PR Review — fix(fleet): four seat-path fixes

  • Gather context and understand the request
  • Read changed files
  • Review each fix area
  • Post complete review

Five distinct fixes (the PR body says four, but I count the undeliveredAssigns → assignsByChange rename + undeclared-row enrichment as a sixth sub-fix — both live in the same commit). Reviewed at 925b785.

Fix 1 — Windows console window (detach_windows.go)

Correct. CREATE_NO_WINDOW (0x08000000) replaces DETACHED_PROCESS (0x00000008). The MSDN note that the two flags are mutually exclusive is real; having both was undefined behaviour. CREATE_NEW_PROCESS_GROUP (0x00000200) stays so Ctrl+C from the parent doesn't propagate.

The test is tight — it asserts noWindow set, newGroup set, detachedProcess NOT set. The portability workflow runs it on windows-latest, which is the only machine where the build tag fires and the window problem would manifest. Good.

Fix 2 — Pool label inherits from roles.map, not directory name (views.go)

Correct and well-reasoned. poolLabel resolution order:

  1. Checkout's own map line (when tenant matches) — exact binding wins.
  2. Sibling rows sharing the same git repo ID under the selected tenant — unanimous label from existing seats.
  3. Ambiguity refusal when siblings disagree.
  4. base as last resort (bootstrap: no existing seats at all).

The sameRepoRows helper uses fleet.RepoID (git common dir identity) rather than a path string, so symlinked or renamed checkouts still resolve correctly.

Minor inefficiency: cmdPool calls poolTenant then poolLabel; each calls sameRepoRows, which each reads fleet.RolesMap() from disk. Under a lock elsewhere, not a correctness issue — just two reads of the same file in one top-up run. Worth noting for future consolidation, not blocking.

Scope of ambiguity check: poolLabel considers ALL bindings for the repo+tenant (non-seat hand-roled checkouts included), not just pool seats. This is intentional and correct: consistent labelling across all kinds of roles for a repo matters. A hand-roled checkout with a different label will block fleet pool, which is the right defence.

TestPoolAmbiguityRefusesBeforeCreatingSeats verifies the refusal happens before the worktree is created and the map is written — important for atomicity.

Fix 3 — assignCheckout reports the right git error (views.go)

Correct. The old structure ran both checkout and checkout -b … origin/… and reported the latter's message. The fallback can only ever say "branch already exists" (because the first attempt just established that), while the first attempt's message names the worktree holding the branch — the actual diagnostic.

New structure is clear: try first, if that fails try fallback, if fallback also fails surface first. The fallback's diagnostic is deliberately discarded (rc2, _), with an explanatory comment.

TestAssignReportsHoldingWorktree proves the error message carries the path of the holding checkout, exercising the exact case described.

Fix 4 — writeDenies surfaces extras (role.go)

Correct. Signature changes from ([]string, error) to (deny, extra []string, error) — the only caller is roleUnderLock (same file), no other callers exist in the package.

The merge logic itself is unchanged; extra is computed as a by-product of the existing loop. The note emitted in roleUnderLock is appropriately phrased: it names the count, the specific denies, and the file to edit. The PR correctly calls out that the signal matters more than the fix — a narrowing that silently doesn't take is worse than one that says so.

TestWriteDeniesReportsAndPreservesExtras covers the three-way case (old+shared in existing, shared+new in manifest, old is the extra) and also verifies the unrelated key survives the rewrite. Solid.

Fix 5 — install.sh --rollback glob (install.sh + install_test.go)

Correct. The old "$f".bak-* glob could match settings.json.bak-env or any other sibling. The new glob "$f".bak-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9] anchors to the exact YYYYMMDD-HHMMSS shape that the script itself writes (stamp="$(date +%Y%m%d-%H%M%S)").

The comment documents why with a concrete example (settings.json.bak-env). The test (TestInstallerRollbackIgnoresUnrelatedBackup) writes both a valid timestamp backup and the bak-env sibling, runs --rollback with HOME overridden, and asserts only the expected backup was restored. This is the right test structure — no mocking, exercises the actual shell function.

Fix 6 — Undeclared-row enrichment + assignsByChange rename (work.go / views.go)

Correct. undeclaredRows previously built each row with all nil columns for for, by, slot, brief. When a seat assignment exists for (repo, branch), those four fields are now populated from it — same data fleet already wrote, just read back. The state stays undeclared (correct: an assignment is not an ownership declaration).

The rename from undeliveredAssigns to assignsByChange fixes a misleading name: the old function returned ALL assignments regardless of delivered_to, and the old name implied filtering that wasn't there. assignedFor (the Unowned path) applies the delivered_to == "" filter itself, which is still correct. The undeclaredRows path correctly does NOT filter by delivery status: you want context from any assignment, delivered or not.


Summary

All five (six) fixes are minimal, correctly diagnosed, and backed by tests that would have caught the original defect. The PR description is unusually clear about what was observed vs. inferred, and TestFR1NoDomainWordsInTheSubstrate being mentioned as having done its job is a good signal the suite is actively used.

Two non-blocking observations:

  • The double roles.map read in cmdPool (via poolTenantpoolLabel) is a future consolidation candidate, not a bug.
  • fleet-portability.yml runs a targeted subset of the fleet suite on macOS and Windows. The full ./cmd/fleet/... suite still runs on Linux in the existing CI. The split is reasonable.

No correctness issues. Ready to merge.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-09T03:00:17.664496Z b16c523 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@itsHabib itsHabib changed the title fix(fleet): four seat-path fixes found standing up a real pool fix(fleet): preserve pool identity and improve seat diagnostics Sep 9, 2026
@itsHabib

itsHabib commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

Reviewed source head 0af61008b5e870e49a663b4f08dc30e41a5e71f9 on macOS and pushed the authorized enhancement at 925b785a8076d9dbdc594cb89f0b47a9b9ae6700.

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 (tenant="first", err=nil for conflicting sibling tenants). The fix refuses ambiguous inheritance before creating seats and limits labels to the selected tenant; exact checkout bindings retain precedence.

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

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Reviewer: claude
Reviewed commit: 925b785a8076d9dbdc594cb89f0b47a9b9ae6700

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread cmd/fleet/internal/verbs/work.go Outdated
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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 assign reports 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.

Comment thread cmd/fleet/internal/verbs/views.go Outdated
Comment thread cmd/fleet/internal/watch/detach_windows.go Outdated
@itsHabib

itsHabib commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@itsHabib

itsHabib commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@itsHabib
itsHabib requested a lite review from Copilot September 9, 2026 02:53
@claude

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown

Claude finished @itsHabib's task in 2m 16s —— View job


PR Review — fix(fleet): preserve pool identity and improve seat diagnostics

  • Gather context and understand the request
  • Read changed files (full diff + new commit 077fcd9)
  • Review each change area
  • Post complete review

Reviewing at HEAD 077fcd9. The previous Claude pass covered 925b785; the three changes in 077fcd9 address the Codex and Copilot findings. All eight commits reviewed together below.


Commit 077fcd9 — three targeted fixes

Fix A — holderAssignment (Codex P2: wrong seat's metadata on board)

Correct. The old path called assignsByChange() once before the loop, which collapses all assignment files for a (repo, branch) pair by filesystem iteration order. When a branch has been assigned to multiple seats over time, whichever file sorts last wins — the assignment from z-old.json can appear on the row for the current holder in a-current.json.

The new holderAssignment(repo, branch, sid string) function is the right fix:

  1. Reads fleet.SessionRecord(sid) to get the holder's slot — the session record is the ground truth for which seat this session is in.
  2. Constructs the expected path fleet.Safe(slot)+".json" and reads it directly — no directory scan, no iteration-order sensitivity.
  3. Validates all three identity fields (repo, branch, slot) before trusting the record.
  4. Guards against delivery to a different session: delivered_to != "" && delivered_to != sid returns nil, so context delivered to "other" is correctly withheld while an undelivered assignment still returns (a session that hasn't read its own assignment yet still gets the accountability columns).

assignsByChange() correctly stays in place for the Unowned path in views.go, where a per-holder context isn't needed and the map collapse is fine.

TestUndeclaredUsesHolderAssignment covers both cases: current-holder assignment attaches, and delivery-to-other withholds. This is the exact fixture described in the Codex finding.

Fix B — poolTenant ancestor binding (Copilot finding)

Correct. The previous 925b785 code:

if tenant = fleet.TenantOf(checkout); tenant != "" {
    return tenant, nil
}

fleet.TenantOf does longest-prefix matching, so a parent-directory binding resolved a tenant and returned before the sibling ambiguity check could fire. That silently hid the "first" vs "second" conflict that TestPoolAncestorDoesNotHideSiblingAmbiguity now exercises.

The new resolution order:

  1. Explicit --tenant argument — authoritative.
  2. fleet.MapRowsFor(checkout) with role != "" && inherited != "" — the checkout itself has an exact roles.map entry; that tenant is authoritative.
  3. Sibling scan via sameRepoRows(checkout) — unanimous → return it; multiple → refuse before creating any seat.
  4. inherited from step 2 when seen is empty — ancestor-prefix inheritance as last resort before env.
  5. ORG_TENANT env → refuse.

The key invariant: ancestor inheritance only fires when sameRepoRows yields nothing, so a parent-directory binding cannot hide a sibling conflict. The TestPoolAncestorDoesNotHideSiblingAmbiguity test fixture (filepath.Dir(repo)+" inherited lead:parent" plus two conflicting sibling tenants) failed on 925b785 and passes on 077fcd9.

One observation (non-blocking): poolTenant and poolLabel each call sameRepoRows(checkout), which each reads fleet.RolesMap() from disk. That's the same file read twice per top-up run. Still not a correctness concern — noted here as a future consolidation candidate.

Fix C — syscall.CREATE_NEW_PROCESS_GROUP (detach_windows.go)

Correct. Uses the exported syscall.CREATE_NEW_PROCESS_GROUP constant instead of the local const createNewProcessGroup = 0x00000200. The author correctly notes that CREATE_NO_WINDOW is not exported by the Go syscall package in this toolchain, so that one stays as a named local constant. Using the stdlib name where available and documenting the gap where it isn't is the right call — it's not worth pulling in a platform-specific dependency for one constant value.


Minor nit (non-blocking)

cmd/fleet/internal/verbs/work_assignment_test.go has import order fleet before testing. Go convention and goimports put stdlib first. This won't fail CI (gofmt -l doesn't reorder; go vet and golangci-lint with standard rules don't flag import order). Worth a manual tidy but not a blocker.


Full PR summary (all 8 commits)

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.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Reviewer: claude
Reviewed commit: 077fcd960a19fe2e813f9854f46f7508f1bcd79b

@itsHabib

itsHabib commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@itsHabib

itsHabib commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

@claude review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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

  • extra can 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

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown

Claude finished @itsHabib's task in 2m 2s —— View job


PR Review — fix(fleet): preserve pool identity and improve seat diagnostics

  • Gather context and understand the request
  • Read changed files and new commit (b16c523)
  • Review Copilot's open finding on duplicate extra denies
  • Post complete review

Reviewing at HEAD b16c523. The previous two Claude passes covered 925b785 and 077fcd9 in full and found no correctness issues; this pass covers the one new commit and the outstanding Copilot finding that wasn't retracted.


Commit b16c523 — normalize Windows paths in assignment diagnostics

Two test-only changes.

pool_identity_test.goTestAssignReportsHoldingWorktree path comparison

Correct. The previous assertion:

strings.Contains(strings.ReplaceAll(err.Error(), "\\", "/"), strings.ReplaceAll(repo, "\\", "/"))

could fail on Windows when git emits an error containing an 8.3 short-form component (RUNNER~1) while repo holds the long form (Runner), or when case differs (C:\Users vs C:\USERS). The new form:

strings.Contains(fleet.NormCase(strings.ReplaceAll(err.Error(), "\\", "/")), fleet.NormCase(fleet.LongPath(repo)))

applies LongPath to the expected path (expands 8.3 → long form on Windows, no-op on POSIX) and NormCase to both sides (lower-case on Windows, identity on POSIX). This makes the assertion portable without obscuring what it is checking: "the error message contains the holding checkout path."

work_assignment_test.go — import ordering

Correct. Moves "testing" before the third-party fleet import to match Go stdlib-first convention. goimports order, not a logic change.


Copilot open finding — writeDenies can surface duplicate extra denies

Valid, low-severity. The finding at role.go:458 is real:

for _, d := range fleet.Strs(perms, "deny") {
    denySet[d] = true
    if !fromManifest[d] {
        extra = append(extra, d)  // no guard against duplicates in fleet.Strs output
    }
}

fleet.Strs preserves duplicates from whatever slice is in the JSON. If a hand-edited settings.local.json lists "Bash:*" twice, extra gets it twice, and the NOTE prints "2 deny(s) kept: Bash:*, Bash:*" — misleading count, correct semantics (the merge output is already deduplicated via denySet).

Practical impact: Any file that fleet itself wrote is already deduplicated (because writeDenies writes sortedKeys(denySet)). Only a hand-edited file can trigger this. The merge output and the written file are always correct; only the diagnostic NOTE message can overcount.

Fix is a one-liner — guard on !denySet[d] before appending (check must precede the denySet[d] = true assignment):

for _, d := range fleet.Strs(perms, "deny") {
    if !denySet[d] && !fromManifest[d] {
        extra = append(extra, d)
    }
    denySet[d] = true
}

This matches how most Go set-accumulation patterns work and requires no new data structure. Whether to fix it in this PR or defer to FOLLOWUPS.md is a judgment call — it's not blocking since only hand-edited malformed files are affected and the merge itself is always correct.


Summary

b16c523 is a correct, targeted portability fix — the LongPath + NormCase pair is the right tool for this class of Windows path comparison and matches the pattern already established elsewhere in the fleet package. Import ordering change is cosmetic.

The Copilot duplicate-extra finding is valid but low-severity: merge correctness is unaffected, only the NOTE message can miscount for malformed hand-edited files. Everything else was cleared in the prior two passes.

The PR is ready to merge. If the duplicate-NOTE issue is worth a third commit, the one-liner above is the fix; otherwise a FOLLOWUPS.md entry is appropriate.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Reviewer: claude
Reviewed commit: b16c52387fcaadf35a63065c36845ba3aa74e6ab

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: b16c52387f

ℹ️ 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".

@itsHabib

itsHabib commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

Mac continuation — validation and handoff

  • Final code/test head: b16c52387fcaadf35a63065c36845ba3aa74e6ab.
  • Current head: aefbe14f75ecfe35e668eca4ff10886d7304ba7b; its only additional change is FOLLOWUPS.md recording the capped review residuals.
  • CI and macOS/Windows portability jobs passed on both heads.
  • Mac Fleet race tests, vet and lint passed. The actual native binary also passed the full fixture suites through both adapter faces: 364 Claude scenarios and 367 Codex scenarios. These are simulated harness events in disposable state, not proof of the work desktop's effective hook wiring.
  • Claude and Codex completed on b16c523; neither found a blocking correctness issue. Copilot completed on 077fcd9, with the duplicate-extra-deny diagnostic residual; a final refresh was requested but has not produced an exact-b16c523 review record. Do not label that reviewer exact-head complete. Initial panel plus two fix rounds are exhausted; no fourth cycle was requested.
  • Deferred: a manually duplicated deny can overcount the retained-denies NOTE. Written permissions remain deduplicated. Repeated roles.map reads are also recorded as a possible future consolidation. These are recorded residuals, not a grant or judgment accepting them.

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 021ee15f5334344bdb9e63658d08541e35d182a0:
https://github.com/itsHabib/cc-skills/blob/021ee15f5334344bdb9e63658d08541e35d182a0/docs/features/fleet-policy-boundary/workday-handoff.md

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.

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.

2 participants