Skip to content

Fleet install for shared hosts and virtual desktops: per-user collector, Linux linger, Windows all-users logon task, Intune + Ansible recipes - #5950

Merged
vivekchand merged 2 commits into
mainfrom
feat/fleet-install-vdi-5942
Sep 15, 2026
Merged

vivekchand merged 2 commits into
mainfrom
feat/fleet-install-vdi-5942

Conversation

@vivekchand

@vivekchand vivekchand commented Sep 13, 2026 •

Copy link
Copy Markdown
Owner

Refs #5942

Product record: https://factory.8090.ai/project/b415065f-ab2f-4f53-8864-0c009fd098cb/requirements/d673cdaa-3bdb-4b10-8a2d-d40ccb5ae630
Design: https://factory.8090.ai/project/b415065f-ab2f-4f53-8864-0c009fd098cb/blueprints/ae944090-9c2b-4549-8ec2-f858736a747b (new "Fleet install on shared and virtual desktops" section)

Review fixes (head 63e602a, rebased on main)

Blocking: no unattended self-update from an administrator-owned install. Both recipes install where only an administrator can write (C:\Program Files\ClawMetry\venv, /opt/clawmetry-fleet), and auto-update is on by default. On Windows the update plan is exit-and-respawn. Every signed-in user's collector exited, the helper ran pip three times against a directory it cannot write, then relaunched. The in-memory backoff died with the process, so the cycle repeated every few minutes.

  • routes/update_check._maybe_auto_update now skips when the running virtual environment's site-packages is not writable by this user. The check is probed once per process with a real file create, because os.access ignores ACLs on Windows. A non-venv interpreter is unchanged, because pip falls back to a per-user install there. Nothing is set in the recipes; the collector detects it.
  • service status reports auto_update: {state: off, basis: install_not_writable} with how to update (re-run the recipe with a new pin). The README "Pin, update, rollback" section and the Intune script header now say exactly that.
  • New criterion AC-OBS-FLEET-001.4 added to the requirement and mirrored; baseline tightened (117/187 after rebasing onto main).
  • Guard: test_auto_update_never_attempted_from_unwritable_fleet_install runs the Windows respawn plan three times and asserts no pip, no exit and no respawn, plus a control run showing the same harness does respawn on a writable install. test_self_update_blocked_only_for_unwritable_isolated_env uses a real 0500 directory.
  • Revert proof (local, restored afterwards):
    • removing the skip fails the first test (1 failed)
    • forcing the probe to "writable" fails the second (1 failed)
    • restored: 25 passed
  • Real runners: the Ubuntu job asserts that each user's status against the root-owned /opt venv reports install_not_writable. The Windows job puts a deny ACE for the runner account on a directory and asserts the probe reports it unwritable, and a writable directory not blocked.

Found by the real runner: the shared environment was writable by every desktop user. The first Ubuntu run with the new assertion showed the Ansible-created /opt/clawmetry-fleet/lib/python3.12/site-packages at mode 0o40777: root ran under a 0000 umask, and user cmalice could create a file there. Any user could have planted code that every other user's collector imports. The self-update probe was right to call it writable, and this is worse than the review item.

  • Ansible: venv creation and pip run under umask 022, then the playbook enforces owner: root and mode: u=rwX,go=rX recursively with follow: false. CI fails on any group-writable, world-writable or non-root path under the environment.
  • Intune: explicit ACL on the install directory (SYSTEM and Administrators full control, Users read and execute, inheritance removed), so a custom -InstallDir cannot inherit "Authenticated Users: Modify". CI fails if Users, Authenticated Users, Everyone or Interactive holds a write-type right.
  • Guard: test_recipes_keep_the_shared_environment_unwritable_by_desktop_users. Proof that the checks catch the bug: run 34793069392 on 875adc2, before the recipe fix, printed mode 0o40777 ... CREATE SUCCEEDED for cmalice.
  • Another session pushed a chmod -R go-w task for the same finding (3177291). It is folded into the above; see the PR comment.

Found by the Windows runner: the first write probe could hang. Under the deny-ACE check the step ran 24 minutes, until the job timeout. CPython's tempfile.mkstemp retries a PermissionError on Windows whenever os.access, which ignores ACLs, says the directory is writable, up to TMP_MAX names. A collector's first update check would have hung the same way. The probe now makes one exclusive os.open of a random name and treats a denied create as not writable. test_write_probe_is_one_attempt_never_a_mkstemp_retry_loop asserts one attempt and no mkstemp, and it fails against the previous implementation. The CI step has a 3-minute timeout so a regression fails fast.

Non-blocking items also addressed:

  • A Windows data directory is reported private: null (windows_profile_acl_not_checked) instead of a claimed yes, and the Windows job asserts that.
  • Linux enables linger before registering the unit, with loginctl --no-ask-password.
  • CLI install text uses plain words instead of state codes.
  • The Intune script refuses a -PythonExe that lives in a user profile.
  • The README documents the machine-wide interpreter requirement and the possible console window at sign-in. The console window is unverified; there is no multi-session host.

Left as is: the respawn failure backoff is still in-memory for other failure causes (pre-existing, not fleet-specific).

The VDI profile this implements

One collector per user, running as that user, registered per machine where the OS allows it. The issue refinement asked us to validate a privileged all-user service before assuming it. We rejected it for this increment: the collector reads agent files in each user's profile and writes a store holding their session content, so one SYSTEM service would be able to read every user's content and would attribute everyone to one node.

Linux Windows
Registration per-user systemd unit + logind linger one logon task for BUILTIN\Users (--all-users, elevated)
Runs as the user each signed-in user, least privilege, instances policy Parallel
After logoff keeps running (linger) stops with the session, resumes at next sign-in; status says so
Data ~/.clawmetry restricted to 0700 by fleet install profile ACL (reported as not checked)
Updates re-run the recipe with a new pin; no self-update from /opt re-run the recipe with a new pin; no self-update from Program Files

What changed

  • clawmetry service install | status | uninstall (clawmetry/fleet_install.py, wired in clawmetry/cli.py including the _subcmds routing list, --json for scripts). Exit 0 only when the requested state was actually reached. Linux linger refused: state denied plus sudo loginctl enable-linger <user>, exit 1. Windows not elevated: needs_admin.
  • clawmetry uninstall on Windows now also removes the per-user and all-users scheduled tasks (before, the per-user task survived uninstall).
  • deploy/fleet/intune/Install-ClawMetry.ps1, deploy/fleet/ansible/clawmetry.yml, deploy/fleet/README.md (pin, update, rollback, uninstall, proxy/CA/offline reuse, enrollment guidance). No credential in either recipe.
  • No new HTTP route, so no cloud route-policy PR is needed.

Verification

Unit tests

  • tests/test_fleet_install.py (27 tests after the review fixes, in the ci.yml unit list).
  • Revert proof, run locally by mutating fleet_install.py and restoring it:
    • task principal SYSTEM instead of BUILTIN\Users: 1 failed
    • no chmod of the data dir: 1 failed
    • linger reported enabled without checking logind: 2 failed
    • instances policy IgnoreNew: 1 failed
    • restored: 20 passed
  • Also green locally: tests/test_daemon_registration.py, tests/test_uninstall_windows.py, tests/test_cli_help_no_dashboard_import.py.
  • CLI smoke in a scratch venv with a scratch HOME on macOS: service status (text and JSON), service install --all-users (unsupported, exit 1), service --help.

Real runners (.github/workflows/fleet-install-test.yml; first green on b00080b, rerun on 63e602a with the review assertions)

Ubuntu, Ansible playbook, users cmalice and cmbob:

  • Both users show linger enabled and survives_logoff: true, with no login session for either user.

  • Each collector's pid is owned by its own user.

  • Neither user can list or read the other's ~/.clawmetry. Homes were deliberately left 0755, so this tests the directory mode, not the distro default.

  • Uninstall removes the unit.

  • Idle overhead, runner reference only (not a VDI image, no agent workload). CPU is a lifetime average over about 65 s, so it includes startup:

    user RSS CPU data dir
    cmalice 137 MB 2.3% 152K
    cmbob 143 MB 2.4% 152K
  • Not exercised on the runner: the linger denied path. The runner's polkit let a user with no session enable her own linger, and the job asserts only that the reported state matches logind, which it did. The denied path and its admin command are covered by unit tests.

Windows, Intune script, elevated. The exported task XML contains:

  • <GroupId>S-1-5-32-545</GroupId>, no UserId
  • <MultipleInstancesPolicy>Parallel</MultipleInstancesPolicy>
  • <LogonTrigger /> with no user
  • the venv python.exe -m clawmetry.sync action

RunLevel is omitted because LeastPrivilege is the schema default, and the check fails on any explicit other value. status reports supervisor: task_all_users and survives_logoff: false, and uninstall removes the task.

  • CodeQL: no new alerts. The earlier chmod findings were in test fixtures; they now create the directory under a 022 umask instead of calling chmod.
  • AC mirror: AC-OBS-FLEET-001.1 through 003.2 added to docs/acceptance_criteria.json, each declared by a test; baseline tightened (108/178).

No real Azure Virtual Desktop or multi-session host was available. Everything above ran on GitHub-hosted runners. On Windows, CI cannot sign in two interactive users, so one instance per signed-in user follows from the task definition and was not observed.

Remaining on #5942 (not in this PR)

  • Observed on a real AVD multi-session host: two users' sessions attributed separately, plus FSLogix/profile roaming and logout cleanup.
  • Fresh node identity for pooled image clones, and deliberate attribution for returning users (identity work, Attribution: project + user tags and per-project budgets (burn vs budget) #5941).
  • Capped, recoverable buffering through a one-hour outage, and receipts (OTLP intake: durable acknowledgements, recovery and unsampled usage accounting #5949).
  • Scoped, rotatable enrollment keys distinct from the account key (cloud).
  • Overhead measured on the chosen image under a real workload. CI only records an idle runner reference point.
  • Windows process and pre-tool control capability states verified separately on a multi-session host.
  • Keeping a Windows collector alive after logoff, or a service-to-user mapping, if that profile is chosen later.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jm9d7s4fN55hN3YzQo75o9

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

Comment thread tests/test_fleet_install.py Fixed
Comment thread tests/test_fleet_install.py Fixed
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

Comment thread tests/test_fleet_install.py Fixed
Comment thread tests/test_fleet_install.py Fixed
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@vivekchand

Copy link
Copy Markdown
Owner Author

Coordinator review

Reviewed head b00080b718 against #5942 (including the 2026-09-13 refinement), the Factory requirement d673cdaa, and the code paths the fleet collector runs through. No code was changed.

Verdict: fix before merge (1 blocking item).

Blocking

1. On a Windows --all-users host, every user's collector keeps exiting and relaunching: auto-update tries to write into an admin-owned venv.

I traced this in the code. It was not observed, and CI could not show it (see the last point).

  • Install-ClawMetry.ps1 installs into C:\Program Files\ClawMetry\venv as admin/SYSTEM. Each user's collector then runs from that venv with least privilege, so it cannot write to it.
  • Auto-update is on by default for every role (routes/update_check.py::_maybe_auto_update). A pinned fleet is behind PyPI almost all the time, given how often releases ship.
  • On Windows _restart_plan() always returns "respawn" (line 143). The collector exits. clawmetry/update_respawn.py then runs pip install --upgrade 3 times (waits of 20s, 60s and 120s), fails with access denied, and relaunches the collector.
  • On this path the failure never reaches the backoff. _failed_update_attempts is in memory only, and last_attempt_* is written but never read to skip a target. The relaunched collector hits the same target at its next 60-second check.
  • Result: each signed-in user's collector is down most of every ~5 minutes and runs pip 3 times per cycle, on a multi-session host with N users. Meanwhile service status still reports the task as registered.
  • The README's "Pin" section says to set CLAWMETRY_AUTO_UPDATE=0 "in the environment the collector runs with". The Windows task has no environment block, and neither recipe provides a way to set it.
  • CI could not catch this: _env_auto_update_disabled() treats CI=true as disabled, so the green Windows job never exercised auto-update.

Fix, any one of these, with a regression test:

  • The fleet install disables auto-update for collectors it registers. For example, the Intune script sets a machine-level CLAWMETRY_AUTO_UPDATE=0, and the Ansible playbook does the same through a systemd user drop-in or an Environment= line. Add a test asserting both recipes do it.
  • Or _maybe_auto_update skips (and reports "managed install, not writable by this user") when the interpreter prefix is not writable, and saves the backoff to disk so a respawn cannot retry immediately.

Then make README "Pin, update, rollback" describe what the recipe actually does.

On Linux, a root-owned /opt/clawmetry-fleet fails perform_self_update with no fallback to a folder in the user's home. The 30-minute in-memory backoff bounds that, so it is not a loop, but the same fix should cover it.

Non-blocking (worth doing, not merge gates)

  • Windows "Data private: yes" is assumed, not checked. data_dir_private() returns private: True on Windows without looking at the ACL. The text output hides the basis. That reads against AC-OBS-FLEET-002.2 and the PR's own "never report coverage that is not there" rule, and redirected or FSLogix profiles can differ. Suggest private: null / "not checked (profile ACL)".
  • Likely a visible console window for every user at sign-in. The all-users task runs python.exe (a console program) in each interactive session. Closing that window stops collection until restart-on-failure. The per-user task already behaves this way, but now it reaches every user on the host. It can't be seen on CI. Consider pythonw.exe or a hidden launcher, and check on a real host.
  • -PythonExe must be a machine-wide interpreter. A Windows venv's python.exe forwards to the base interpreter named in pyvenv.cfg. If the admin's Python is a per-user install under AppData, every other user's collector fails to start. Document this or check for it in the script.
  • Linux install order. install() registers the user unit before enabling linger. For a user whose user manager isn't running, the unit step fails first and a rerun is needed. Enabling linger first avoids that. Also consider loginctl --no-ask-password so a polkit TTY prompt cannot eat the 15s timeout.
  • Raw state codes. CLI text output prints codes such as needs_admin, denied and unavailable. Each comes with a reason, but plain words would read better.

Checked and fine

vivekchand added a commit that referenced this pull request Sep 14, 2026
…install

Review of #5950: both fleet recipes install into an environment only an
administrator can write, while auto-update is on by default. On Windows the
respawn plan exited every signed-in user's collector, pip failed three times,
and the in-memory backoff died with the process, so it repeated every few
minutes while status still said registered.

- routes/update_check: skip auto-update when the running venv's
  site-packages is not writable by this user (probed once per process with a
  real file create, since os.access ignores Windows ACLs). Non-venv
  interpreters are unchanged: pip falls back to a per-user install there.
- service status reports auto_update off with basis install_not_writable
  and how to update (re-run the recipe with a new pin); README Pin section
  and the Intune script say the same.
- Windows data_dir privacy is reported as not checked instead of yes.
- Linux enables linger before registering the unit; loginctl
  --no-ask-password; plain-words CLI install text.
- Intune script refuses a Python that lives in a user profile.
- New criterion AC-OBS-FLEET-001.4 mirrored; baseline 109/179.
- CI: Linux asserts the root-owned /opt venv reports install_not_writable
  as each user; Windows proves the probe honours a deny ACE.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm9d7s4fN55hN3YzQo75o9
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@github-actions

github-actions Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Visual diff

Comparing dacf990beafa (head) against the PR base branch.

49 of 70 comparison(s) flagged (>1% pixel diff).

View Before After Diff
desktop overview before after diff · 0.01%
desktop flow before after diff · 0.04%
desktop brain ⚠️ before after diff · 100.00%
desktop usage ⚠️ before after diff · 100.00%
desktop crons before after diff · 0.00%
desktop memory before after diff · 0.04%
desktop security ⚠️ before after diff · 100.00%
desktop subagents ⚠️ before after diff · 100.00%
desktop transcripts before after diff · 0.00%
desktop logs ⚠️ before after diff · 7.23%
desktop skills before after diff · 0.01%
desktop models before after diff · 0.00%
desktop approvals before after diff · 0.25%
desktop alerts ⚠️ before after diff · 100.00%
desktop notifications before after diff · 0.00%
desktop limits ⚠️ before after diff · 100.00%
desktop history ⚠️ before after diff · 1.62%
desktop channels ⚠️ before after diff · 100.00%
desktop harness ⚠️ before after diff · 100.00%
desktop inventory ⚠️ before after diff · 1.62%
desktop nemoclaw ⚠️ before after diff · 100.00%
desktop guard ⚠️ before after diff · 2.74%
desktop signals ⚠️ before after diff · 2.99%
desktop policy ⚠️ before after diff · 100.00%
desktop selfevolve ⚠️ before after diff · 1.85%
desktop swimlane before after diff · 0.36%
desktop tool-catalog ⚠️ before after diff · 100.00%
desktop tracing ⚠️ before after diff · 100.00%
desktop turn-anatomy ⚠️ before after diff · 3.14%
desktop version-impact ⚠️ before after diff · 100.00%
desktop context-economics ⚠️ before after diff · 100.00%
desktop agents ⚠️ before after diff · 1.50%
desktop evals ⚠️ before after diff · 16.79%
desktop bench ⚠️ before after diff · 100.00%
desktop trail ⚠️ before after diff · 2.00%
mobile overview before after diff · 0.01%
mobile flow ⚠️ before after diff · 5.59%
mobile brain ⚠️ before after diff · 6.63%
mobile usage ⚠️ before after diff · 100.00%
mobile crons ⚠️ before after diff · 1.69%
mobile memory ⚠️ before after diff · 4.64%
mobile security before after diff · 0.03%
mobile subagents ⚠️ before after diff · 100.00%
mobile transcripts before after diff · 0.01%
mobile logs ⚠️ before after diff · 1.66%
mobile skills ⚠️ before after diff · 1.69%
mobile models ⚠️ before after diff · 2.81%
mobile approvals ⚠️ before after diff · 100.00%
mobile alerts ⚠️ before after diff · 3.50%
mobile notifications ⚠️ before after diff · 4.96%
mobile limits ⚠️ before after diff · 2.40%
mobile history before after diff · 0.99%
mobile channels before after diff · 0.02%
mobile harness ⚠️ before after diff · 100.00%
mobile inventory before after diff · 0.01%
mobile nemoclaw ⚠️ before after diff · 100.00%
mobile guard before after diff · 0.00%
mobile signals ⚠️ before after diff · 4.25%
mobile policy ⚠️ before after diff · 1.61%
mobile selfevolve ⚠️ before after diff · 1.61%
mobile swimlane before after diff · 0.02%
mobile tool-catalog ⚠️ before after diff · 1.61%
mobile tracing ⚠️ before after diff · 1.17%
mobile turn-anatomy ⚠️ before after diff · 2.85%
mobile version-impact before after diff · 0.01%
mobile context-economics ⚠️ before after diff · 3.60%
mobile agents ⚠️ before after diff · 1.69%
mobile evals before after diff · 0.75%
mobile bench before after diff · 0.01%
mobile trail ⚠️ before after diff · 3.26%

Folder: dacf990beafa. Full PNGs also attached as a workflow artefact.

Generated by visual-diff bot. Pixel diffs >1% flagged; eyeball the table before merging. This check is non-blocking — fail = bot bug, not a code problem.

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Sep 14, 2026

Copy link
Copy Markdown
Owner Author

CI fix pushed (commit 3177291).

Root cause: python -m venv /opt/clawmetry-fleet runs as root under a 0000 umask on GitHub Actions Ubuntu runners, producing mode 0o40777 (world-writable) on all venv directories including site-packages. The auto-update probe (tempfile.mkstemp(dir=site_packages)) correctly detected the directory as writable and left auto-update enabled -- so the CI assertion state == "off" failed.

Fix: one chmod -R go-w {{ clawmetry_venv }} task added to deploy/fleet/ansible/clawmetry.yml immediately after pip install. Site-packages becomes mode 755 (owner-write only), the probe returns writable: false, and the collector reports auto_update: {state: off, basis: install_not_writable} as AC-OBS-FLEET-001.4 requires. The test was correct -- it caught a real deployment bug.


Generated by Claude Code

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

vivekchand added a commit that referenced this pull request Sep 14, 2026
…install

Review of #5950: both fleet recipes install into an environment only an
administrator can write, while auto-update is on by default. On Windows the
respawn plan exited every signed-in user's collector, pip failed three times,
and the in-memory backoff died with the process, so it repeated every few
minutes while status still said registered.

- routes/update_check: skip auto-update when the running venv's
  site-packages is not writable by this user (probed once per process with a
  real file create, since os.access ignores Windows ACLs). Non-venv
  interpreters are unchanged: pip falls back to a per-user install there.
- service status reports auto_update off with basis install_not_writable
  and how to update (re-run the recipe with a new pin); README Pin section
  and the Intune script say the same.
- Windows data_dir privacy is reported as not checked instead of yes.
- Linux enables linger before registering the unit; loginctl
  --no-ask-password; plain-words CLI install text.
- Intune script refuses a Python that lives in a user profile.
- New criterion AC-OBS-FLEET-001.4 mirrored; baseline 109/179.
- CI: Linux asserts the root-owned /opt venv reports install_not_writable
  as each user; Windows proves the probe honours a deny ACE.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm9d7s4fN55hN3YzQo75o9
@vivekchand
vivekchand force-pushed the feat/fleet-install-vdi-5942 branch from 3177291 to 030b82e Compare September 14, 2026 00:52
@vivekchand

Copy link
Copy Markdown
Owner Author

Branch note: I rebased this branch onto main and force-pushed with a lease pinned to 3177291. That commit came from another session: a merge of main plus a chmod -R go-w task in the Ansible playbook. Its fix is folded in, not dropped. Head is now 030b82e.

  • Same finding. The first real Ubuntu run showed /opt/clawmetry-fleet/lib/python3.12/site-packages at mode 0o40777, and an unprivileged user could create a file there. Any desktop user could have planted code that every other user's collector imports, so the self-update probe was right to call it writable.
  • Linux fix (ceb7355): the playbook creates the venv and runs pip under umask 022. It then enforces owner: root, mode: u=rwX,go=rX recursively with follow: false, so it never changes permissions through the venv's python symlink. CI fails on any group-writable, world-writable or non-root path under the environment.
  • Windows fix: the Intune script sets an explicit ACL on the install directory: SYSTEM and Administrators full control, Users read and execute, inheritance removed. A custom -InstallDir such as C:\ClawMetry therefore cannot inherit "Authenticated Users: Modify". CI fails if Users, Authenticated Users, Everyone or Interactive holds a write-type right.
  • test_recipes_keep_the_shared_environment_unwritable_by_desktop_users pins both recipes and both CI checks.

Please don't re-push the chmod -R go-w task; the playbook already covers it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jm9d7s4fN55hN3YzQo75o9

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Sep 14, 2026
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@vivekchand

Copy link
Copy Markdown
Owner Author

Ready to merge. Head 63e602a: 47 checks pass, 1 skipped, 0 failing, including E2E Gate (required), Drift Bot, CodeQL, the product-record gate and both Fleet install runner jobs. gh pr view reports MERGEABLE / CLEAN. Since the last rebase, main has gained one unrelated commit; it does not conflict, and the branch does not need to be up to date to merge. Not merged.

Blocking review item fixed. A collector running from an administrator-owned fleet install no longer tries to update itself. Before this, on Windows it exited every signed-in user's collector, pip failed three times, and the cycle repeated. The skip is routes/update_check.py plus fleet_install.self_update_blocked. service status reports auto_update: off / install_not_writable and says to update by re-running the recipe with a new pin. The README Pin section says the same. New criterion AC-OBS-FLEET-001.4 is in the Factory requirement and mirrored into docs/acceptance_criteria.json.

Two more bugs the real runners found while verifying that fix, both fixed here:

  • The Ansible venv at /opt/clawmetry-fleet was mode 0777. Any desktop user could have written code that every other user's collector imports. The playbook now runs under umask 022 and enforces root ownership with u=rwX,go=rX. The Intune script sets an explicit ACL on its install directory. Both CI jobs fail if a desktop user can write the environment. Run 34793069392 caught it: mode 0o40777 ... CREATE SUCCEEDED. Run 34796721610 shows it fixed: mode 0o40755, PermissionError, install_not_writable.
  • The first write probe could hang on Windows. tempfile.mkstemp retries a denied create while os.access, which ignores ACLs, says the directory is writable; it ran for 24 minutes on the runner. The probe now makes a single os.open attempt. In run 34796721610 the deny-ACE step took under a second and printed blocked: True ... os.access says writable: True.

Non-blocking review items also done:

  • A Windows data directory is reported as not checked rather than private.
  • Linger is enabled before the unit is registered, with --no-ask-password.
  • The CLI prints plain words instead of state codes.
  • The Intune script refuses a Python installed inside a user profile.

Merge after: nothing. No dependency PR.

Companion PRs: none. There is no new HTTP route, so no cloud_route_policy entry is needed.

Post-merge / post-release verification:

  1. After the next [RELEASE] publishes, check that https://pypi.org/pypi/clawmetry/json lists the new version, and that the wheel contains clawmetry/fleet_install.py and deploy/fleet is in the repo.
  2. On a scratch Linux VM:
    • Run ansible-playbook -i <vm>, deploy/fleet/ansible/clawmetry.yml -e '{"clawmetry_users": ["u1","u2"]}' -e clawmetry_package=clawmetry==<released>.
    • Then sudo find /opt/clawmetry-fleet ! -type l \( -perm -g+w -o -perm -o+w \) must print nothing.
    • sudo -u u1 /opt/clawmetry-fleet/bin/clawmetry service status --json must report survives_logoff: true and auto_update.basis: install_not_writable.
    • After one release lands on PyPI, u1's collector must still be running with the same pid and no pip process run as u1.
  3. On a scratch Windows host, elevated:
    • Run Install-ClawMetry.ps1 -Package clawmetry==<released>.
    • Sign in as a standard user and run "C:\Program Files\ClawMetry\venv\Scripts\clawmetry.exe" service status. It must show Auto-update: off (this user cannot write ...) and Data private: not checked.
    • schtasks /query /tn ClawMetrySyncDaemonAllUsers /xml must show GroupId S-1-5-32-545 and Parallel.
    • Note whether a console window appears at sign-in. That is still unverified.
  4. Uninstall on both hosts with clawmetry service uninstall and confirm the unit or task is gone.

Still not verified (listed on #5942): a real AVD or multi-session host with two interactive users, overhead under a real workload, identity for pooled clones (#5941), buffering (#5949) and scoped enrollment keys.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jm9d7s4fN55hN3YzQo75o9

vivekchand added a commit that referenced this pull request Sep 14, 2026
…install

Review of #5950: both fleet recipes install into an environment only an
administrator can write, while auto-update is on by default. On Windows the
respawn plan exited every signed-in user's collector, pip failed three times,
and the in-memory backoff died with the process, so it repeated every few
minutes while status still said registered.

- routes/update_check: skip auto-update when the running venv's
  site-packages is not writable by this user (probed once per process with a
  real file create, since os.access ignores Windows ACLs). Non-venv
  interpreters are unchanged: pip falls back to a per-user install there.
- service status reports auto_update off with basis install_not_writable
  and how to update (re-run the recipe with a new pin); README Pin section
  and the Intune script say the same.
- Windows data_dir privacy is reported as not checked instead of yes.
- Linux enables linger before registering the unit; loginctl
  --no-ask-password; plain-words CLI install text.
- Intune script refuses a Python that lives in a user profile.
- New criterion AC-OBS-FLEET-001.4 mirrored; baseline 109/179.
- CI: Linux asserts the root-owned /opt venv reports install_not_writable
  as each user; Windows proves the probe honours a deny ACE.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm9d7s4fN55hN3YzQo75o9
@vivekchand
vivekchand force-pushed the feat/fleet-install-vdi-5942 branch from 63e602a to 94ee469 Compare September 14, 2026 03:41
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Sep 14, 2026

Copy link
Copy Markdown
Owner Author

Automated CI triage — Syntax & Lint failure blocking all downstream jobs

The Syntax & Lint job failed (job 103930979359, run 34830041491), causing all downstream CI jobs to be skipped. This is a real failure (not a queue-priority cancellation).

All other checks show as skipped/cancelled because they depend on Syntax & Lint passing. Once the lint issue is fixed and CI reruns, the full suite will execute.

Please check the Syntax & Lint job logs for the specific error.


Generated by Claude Code

vivekchand added a commit that referenced this pull request Sep 14, 2026
…install

Review of #5950: both fleet recipes install into an environment only an
administrator can write, while auto-update is on by default. On Windows the
respawn plan exited every signed-in user's collector, pip failed three times,
and the in-memory backoff died with the process, so it repeated every few
minutes while status still said registered.

- routes/update_check: skip auto-update when the running venv's
  site-packages is not writable by this user (probed once per process with a
  real file create, since os.access ignores Windows ACLs). Non-venv
  interpreters are unchanged: pip falls back to a per-user install there.
- service status reports auto_update off with basis install_not_writable
  and how to update (re-run the recipe with a new pin); README Pin section
  and the Intune script say the same.
- Windows data_dir privacy is reported as not checked instead of yes.
- Linux enables linger before registering the unit; loginctl
  --no-ask-password; plain-words CLI install text.
- Intune script refuses a Python that lives in a user profile.
- New criterion AC-OBS-FLEET-001.4 mirrored; baseline 109/179.
- CI: Linux asserts the root-owned /opt venv reports install_not_writable
  as each user; Windows proves the probe honours a deny ACE.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm9d7s4fN55hN3YzQo75o9
@vivekchand
vivekchand force-pushed the feat/fleet-install-vdi-5942 branch from 872d553 to 84417d7 Compare September 14, 2026 12:29
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Sep 14, 2026
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@vivekchand
vivekchand force-pushed the feat/fleet-install-vdi-5942 branch from a2e227c to 77d3342 Compare September 14, 2026 13:24
@vivekchand

Copy link
Copy Markdown
Owner Author

Branch note: rebased onto main as one commit, head 77d3342f7d (force-push with lease pinned to a2e227c1f9).

Please do not merge main into this branch again; rebase instead.

  • Syntax & Lint was red on 872d553e22: the AC gate reported AC-OBS-FLEET-003.2 as not in the manifest. The real cause: docs/acceptance_criteria.json did not parse. A merge resolution had dropped the }, { before AC-OBS-FLEET-001.1. The later merge b9aca7b88d also added all eight FLEET criteria a second time. Fixed both. check_ac_coverage.py --check: OK, 153/223, ratchet holding.
  • MODULE_MAP: regenerated (257 modules). This is the same content as a2e227c1f9, which the rebase includes.
  • CHANGELOG: this PR no longer touches CHANGELOG.md, so merges stop re-conflicting. The coordinator carries the entry in the [RELEASE] PR. The two merges had also left the entry in the file twice.
  • Verified locally after the rebase:
    • tests/test_fleet_install.py: 27 passed.
    • Also run: module-map drift, workflow YAML and AC-coverage guard tests, check_py39_annotations, and the sync_runtime_count / gen_module_map / gen_framework_coverage / check_ci_test_coverage checks.
    • tests/test_auto_update.py: the same 9 tests fail on a clean origin/main checkout on this machine, so none of them comes from this PR. CI does not run those 9.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jm9d7s4fN55hN3YzQo75o9

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Sep 14, 2026
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Sep 14, 2026
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Sep 14, 2026
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

vivekchand and others added 2 commits September 14, 2026 23:03
`clawmetry service install | status | uninstall`: one collector per user,
running as that user. Linux requests linger (administrator command printed
when refused); Windows --all-users registers one least-privilege logon task
for the Users group. Intune and Ansible recipes install where desktop users
cannot write, and a collector in such an administrator-owned environment
skips unattended self-update (AC-OBS-FLEET-001.4) instead of exiting to
retry an upgrade that cannot succeed. Both recipes are exercised on their
OS in .github/workflows/fleet-install-test.yml.

Rebased onto main as one commit. Repairs from the previous merges:
docs/acceptance_criteria.json did not parse (a missing `}, {` before
AC-OBS-FLEET-001.1) and carried the eight FLEET criteria twice; both fixed,
AC traceability gate OK (153/223, ratchet holding). MODULE_MAP regenerated.
CHANGELOG entry removed from this PR; the [RELEASE] PR carries it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm9d7s4fN55hN3YzQo75o9
@vivekchand
vivekchand force-pushed the feat/fleet-install-vdi-5942 branch from 0405259 to dacf990 Compare September 14, 2026 21:08
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

Copy link
Copy Markdown
Owner Author

⚠️ needs manual rebase — conflict in clawmetry/cli.py, docs/MODULE_MAP.md, docs/acceptance_criteria.json, ci.yml (and others). Cannot auto-resolve: requires code-level judgment to merge correctly.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Blocked on required review — skipping (auto-mergeability sweep). @vivekchand please approve when ready.


Generated by Claude Code

github-actions Bot pushed a commit that referenced this pull request Sep 14, 2026
@vivekchand
vivekchand merged commit 80f4a3f into main Sep 15, 2026
51 of 52 checks passed
vivekchand added a commit that referenced this pull request Sep 15, 2026
…y, cold-load fix, hosted Cost Optimizer data

Carries #5950 (clawmetry service install/status/uninstall, per-user collectors on shared hosts), #5965 (LiteLLM gateway spend by team/person/key), #5957 (cold-load timeouts), #5996 (Cost Optimizer evidence slice), #5967 (CHANGELOG entries).

This triggers PyPI 0.12.880. After it publishes: verify wheel contains clawmetry service and gateway_litellm; wait for cloud auto-pin PR; landing #836 can drop its DO NOT MERGE prefix; cloud #2450 and #2455 can merge after the pin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jgaf95Zzshc3FUBzqNRxiT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants