Task: Show systemd slice/scope info in compass fleet and bearings
Table of Contents
This page documents a task in the Compass quality-of-life improvements: remaining follow-ups story. It captures the goal, current status, acceptance, and any notes or results.
Goal
Surface each environment's systemd scope in compass fleet and compass bearings output, so it is visible which cgroup or scope a session is running under without manually querying systemctl.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Compass quality-of-life improvements: remaining follow-ups |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-08-05 |
Acceptance
- compass fleet lists the scope name per worktree/session where available; compass bearings shows the current session's own scope/slice; falls back gracefully when a session has no named scope.
Plan
Added in compass.py:
_env_systemd_scopes()— queriessystemctl --user list-units --type=scope --output=jsononce and maps each env_name to its running scope types (claude/build/both). Matchesclaude-<env>-<pid>.scopeandbuild-<env>-<slot>-<pid>.scopepatterns via regex. Returns adict; missing keys mean no scope is running for that env._current_session_scope()— reads/proc/self/cgroupand extracts the innermost scope unit name and its parent slice, returning(scope, slice)or(None, None)on failure.
cmd_fleet: calls _env_systemd_scopes() once, adds a SCOPE column (max 14 chars) showing claude, build, build,claude, or — per worktree. Also included in --format json.
cmd_bearings: shows the current session's scope and slice under the environment section (Scope : claude-merry_newton-*.scope (slice: app-claude-merry_newton.slice)), or a fallback message when running unscoped.
Both fall back gracefully when systemd is unavailable (systemctl missing, /proc/self/cgroup unreadable, or no named scope).
Notes
Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario, run
through the QA Validation Runner panel) that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
PRs
| PR | Title |
|---|---|
| #1871 | [compass] Show systemd scope/slice info in fleet and bearings |
Review
Round 1 (bot review)
Round-1 fixes: regex char-class expanded to include hyphens (env_init.py:505 documents hyphenated env names); JSON placeholder fix (None in row dict, — fallback in display only); hoisted duplicate .env lookup into local; removed redundant import json as _json; moved systemctl call after early-return check; removed unnecessary f-prefix.
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | Regex doesn't match hyphenated env names | compass.py | Fixed — added - to char class [a-zA-Z0-9_.-]+? | |
| 2 | SCOPE placeholder leaks into –format json | compass.py | Fixed — scope stores None/null, — applied in display loop only | |
| 3 | Duplicate .env lookup per worktree | compass.py | Fixed — hoisted env_name into local variable | |
| 4 | Redundant import json as _json | compass.py | Fixed — using module-level json | |
| 5 | systemctl call before early-return check | compass.py | Fixed — moved scope_map after worktree guard | |
| 6 | f-prefix on string with no interpolation | compass.py | Fixed — removed f-prefix |
Result
compass fleet now includes a SCOPE column showing claude, build, build,claude, or — per worktree based on running systemd user scopes (one systemctl call, not per-worktree). compass bearings shows the current session's scope and slice under the environment section. Both fall back gracefully when systemd is unavailable or the session runs unscoped.
Two new helpers in compass.py: _env_systemd_scopes() queries systemctl --user list-units --type=scope --output=json and regex-matches claude-<env>-<pid>.scope / build-<env>-<slot>-<pid>.scope patterns. _current_session_scope() reads /proc/self/cgroup to extract (scope, slice).
Acceptance met:
- fleet lists scope name per worktree where available
- bearings shows current session's own scope/slice
- graceful fallback when no named scope exists