Skip to content

fix(daemon): only use the durable user scope when the user manager persists - #24202

Open
brynnclaw wants to merge 1 commit into
stablyai:mainfrom
brynnclaw:fix/daemon-scope-requires-linger
Open

brynnclaw wants to merge 1 commit into
stablyai:mainfrom
brynnclaw:fix/daemon-scope-requires-linger

Conversation

@brynnclaw

@brynnclaw brynnclaw commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

On Linux, Orca puts its terminal helper (the daemon that owns every terminal) into a separate systemd "box" owned by the user's own systemd, so restarting the Orca service doesn't kill your terminals. But that user systemd only stays around after you log out if "lingering" is turned on. When Orca runs as a system service and lingering is off, Orca could still pick that box just because someone happened to be logged in at the time, and then every terminal died a few seconds after that person logged out. Now Orca only uses the box when it will actually last, and otherwise tells you, once, how to turn lingering on (or, if it cannot check, that it cannot tell).

What Changed

Before: isDurableDaemonScopeSupported() said yes whenever systemd was PID 1, a user bus socket existed at /run/user/<uid>/bus (or $XDG_RUNTIME_DIR/bus), and systemd-run --version worked. For orca serve as a system unit with User=<account> and lingering off, that bus exists only while the account has a login session. If one was open when the daemon started, the daemon went into orca-daemon-<nonce>.scope under that transient user manager, and systemd stopped the scope, and every terminal in it, about 10 s after the account's last logout. Orca logged nothing.

After: the probe also requires that the user manager outlives the current login sessions. That holds when either:

  • lingering is enabled for the current user: logind's linger record for that user exists in /var/lib/systemd/linger/ (username from os.userInfo()). logind names the record by systemd's cescape() of the user name (src/login/logind-dbus.c, src/basic/escape.c), so Orca escapes the name the same way over its UTF-8 bytes: a backslash before \, ", ' and the seven C escapes (\a \b \f \n \r \t \v), and \ooo octal for every other byte below 0x20 or from 0x7f up. DOMAIN\orca is looked up as DOMAIN\\orca, josé as jos\303\251, o'brien as o\'brien; or
  • the launching process already runs under this user's manager: its own cgroup (read from /proc/self/cgroup with the module's existing cgroupPathFromProc) contains user@<uid>.service. That covers an app-*.scope under user@<uid>.service and orca serve as a systemctl --user unit, where the daemon can lose the manager no sooner than its launcher does. A plain session-N.scope login does not count: it is a sibling of user@<uid>.service, not under it.

Otherwise the probe returns false, so the daemon takes the existing direct-fork fallback, and Orca logs one line, once per process. When the linger record is missing:

[daemon] Not placing the terminal daemon in a durable systemd scope: lingering is off for <user>, so its user manager stops at the last logout. Run `loginctl enable-linger <user>` so terminals survive a service restart; see docs/reference/headless-linux-server.md.

When Orca cannot check, it does not claim that lingering is off. With no passwd entry for the UID it logs ... cannot tell whether lingering is on: UID <uid> has no passwd entry, so there is no user name to find its linger record by; see .... If the record lookup fails with anything other than ENOENT (an unreadable linger dir, say) it logs ... cannot tell whether lingering is on for <user>: <linger dir> could not be read; see ....

Mechanism details:

  • The new check runs last, after the platform, boot marker, bus and systemd-run --version checks, so the hint is only logged when lingering is the one thing missing (not on macOS, Windows, containers, or hosts without systemd-run).
  • It is read-only (one readFileSync, one access, one userInfo()) and logs at most one line per process. Every failure (unreadable cgroup file, no passwd entry for the UID, a linger lookup that fails with anything but ENOENT) resolves to "not supported". The module's "never throws, fail closed" contract is unchanged.
  • The passwd lookup and the linger-record lookup (currentUsername, isLingering, with the cescape() table) live in a new small module, src/main/daemon/systemd-linger-record.ts, which keeps daemon-cgroup-scope.ts under the repo's 300-line limit.
  • isDurableDaemonScopeSupported and migrateLegacyDaemonScope gain two trailing test seams, lingerDir and ownCgroupPath, next to the existing systemdBootPath / canonicalRuntimeDir ones. Production callers pass nothing and get the real paths.
  • docs/reference/headless-linux-server.md: one short paragraph saying that an open login is not a substitute for lingering, and what Orca does instead.
  • docs/reference/orcad-operations.md: the "All of these must hold" list gains the fourth condition (a user manager that outlives the login sessions: lingering, recorded as a file in /var/lib/systemd/linger/ named after the C-escaped user name, or the launcher under user@<uid>.service as an app-*.scope or a systemctl --user unit, where a plain session-N.scope login does not count). The bus bullet no longer implies that being logged in replaces lingering.

Why

The module header already states the requirement ("a reachable --user manager (a login session, or loginctl enable-linger <user> for a service account)"), and the headless doc already tells operators to sudo loginctl enable-linger orca and says that without it the daemon falls back to the direct launch. The code only checked that the bus existed at probe time. A login session makes the bus exist only while that session lives: logind stops user@<uid>.service UserStopDelaySec (10 s by default) after the last session closes, and the manager stops every scope it owns. So without lingering the outcome depended on login timing, and the "logged in" outcome (the daemon dies at the next logout, silently) was worse than the fallback it was meant to improve on (the daemon dies at the next unit restart, which is documented).

Alternatives considered:

  • Ask logind (loginctl show-user -p Linger, or D-Bus): a process spawn or a bus call on a path that must stay synchronous. logind answers the Linger property, and decides whether to keep the user manager running, with user_check_linger_file() (src/login/logind-user-dbus.c, src/login/logind-user.c): an access() on /var/lib/systemd/linger/ plus the cescape()d user name. Orca does the same lookup with the same escaping, without a spawn or a bus call. For the same user name it gets the same answer. The exception is a name that is not valid UTF-8: Orca reads the name with Node's default userInfo(), which decodes it as UTF-8, so Orca then misses the record and falls back (systemd's own user-name check expects valid UTF-8, so such names are rare).
  • Require lingering only, no cgroup branch: would drop the scope for launchers under user@<uid>.service (an app-*.scope or a systemctl --user unit), where it is correct today, because the launcher itself already lives and dies with that manager.
  • Keep using the scope and only warn: leaves the silent logout kill in place.
  • Enable lingering from Orca: needs privileges and changes host policy; that is the operator's decision, so Orca only names the command.

Linked Issue

Fixes #24201

Visual Proof

N/A: no visual change. This is main-process daemon launch code with no renderer or UI surface; the only user-visible output is one log line.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

New cases in src/main/daemon/daemon-cgroup-scope.test.ts, all with injected fixtures (a real Unix socket as the bus, a temp linger dir, a temp cgroup file), so they never consult the host's own systemd. The suite also stubs os.userInfo() with its own user name, so no fixture reads the host's passwd database and the file passes on a UID without a passwd entry:

  1. is false when the user manager stops at the last logout: no lingering, launched outside it: bus present, systemd present, systemd-run works, no linger file, launcher cgroup is a system service or an SSH session-N.scope → not supported.
  2. is true for a service outside the user manager when lingering keeps that manager alive → supported.
  3. is true without lingering when the launcher already runs under this user's manager → supported under user@<uid>.service, not supported under another UID's user@ manager.
  4. names loginctl enable-linger once per process when lingering is what blocks the scope: no warning while lingering is on; two no-linger probes log exactly one single-line warning that says lingering is off for the user and names loginctl enable-linger <user> and the headless doc.
  5. does not name enable-linger when an earlier gate already rules the scope out: with no linger file, a darwin platform, a missing systemd boot marker, a runtime dir without a bus, and a failing systemd-run --version each give "not supported" and log nothing.
  6. fails closed when its own cgroup file cannot be read and lingering is off → not supported.
  7. fails closed when this UID has no passwd entry, whoever else is lingering, and says it cannot tell: os.userInfo() throws, and the linger dir has files for other names → not supported, and the one warning says it cannot tell whether lingering is on, not that it is off.
  8. finds logind's escaped linger record for <name>, not a raw-name file, for DOMAIN\orca, josé, o'brien and a name with a tab, a double quote and DEL: a record named as logind names it (DOMAIN\\orca, jos\303\251, o\'brien, tab\there\"\177, written out literally in the test) → supported; a file under the raw name → not supported.
  9. fails closed and says it cannot tell when the linger dir cannot be read (skipped as root): the record exists but its dir is mode 000 → not supported, and the warning says it cannot tell.

On an earlier revision of this PR, cases 1, 3, 4, 6 and 7 failed on main with only the tests applied (5 failed, 27 passed). Cases 2 and 5 pass on main too; they guard that the fix keeps the scope where it is still correct and that the check stays after the other gates. Cases 4 and 7 have gained assertions since then, and cases 8 and 9 are new. On this revision, the existing "supported" cases and the legacy-migration case inject a lingering fixture so they stay hermetic.

Local runs for this revision (Linux x64, Node 24.21.0, pnpm 12.0.0, on a shared machine under heavy load from other jobs):

  • vitest run --config config/vitest.config.ts src/main/daemon/daemon-cgroup-scope.test.ts: 37/37 pass. The same tests against the previous revision's source (c47f92f4, before the escaping and the "cannot tell" line): 6 failed, 31 passed. The six were cases 7 and 9 and the four names of case 8.
  • The same file as a UID with no passwd entry (unshare --user --map-user=54321): the previous revision's tests failed 7 of 32, every one from uv_os_get_passwd returned ENOENT raised in the linger fixture. This revision passes 37/37.
  • tsc on a config that extends config/tsconfig.node.json and lists the changed source files, the new module, their callers, the changed test file and one caller's test: 0 errors.
  • oxlint and oxfmt --check on the changed files, and pnpm run check:max-lines-ratchet: pass. The pre-commit hook (lint-staged: oxlint, the react-doctor oxlint config, oxfmt) ran on the commit.

Wider runs were made on the previous revision only and not repeated for this one. All four full commands stopped at the local 8 GB / 1800 s cap: pnpm test and pnpm lint hit the runtime cap, and pnpm typecheck and pnpm build ran out of memory. Every failure in the part of pnpm test that ran was in a file that does not import the changed code, and the first 14 of the 16 pnpm lint steps passed. vitest run --config config/vitest.config.ts src/main/daemon src/main/orcad had 3 failures there, all in files that do not import the changed module directly: two load timeouts (orcad-push-startup, orcad-bundle-native-load-order) and one timing-sensitive assertion (daemon-restore-scrollback-depth). pnpm run build:electron-vite passed there.

CI should run the full four commands.

Not tested: a live orca serve on a systemd host with the logout sequence from the issue, and a real logind record for an account whose name logind escapes (the escaping follows systemd's source and is pinned by case 8). The reproduction in the issue was observed on 1.4.211; the post-fix behaviour on a real host is covered only by the unit tests above.

AI Disclosure

Claude Opus 5.5 (claude-opus-5-5), Anthropic. Used for the investigation, the patch, the tests and this description, directed and reviewed by a human.

Review

AI coding-agent review summary:

  • Cross-platform: Linux only. The new check runs after the existing platform !== 'linux' and /run/systemd/system gates, so macOS and Windows return false exactly as before and never read the new paths. process.getuid absence is handled (the cgroup branch is skipped).
  • SSH/remote/local: no protocol, relay or SSH transport change. Remote hosts running orca serve get the same rule, which is the point of the fix. The SSH-deployed orcad on Linux remotes also reaches this probe: src/main/ssh/orcad-remote-launch.ts starts it with exec nohup inside the SSH login session, and it launches its terminal daemon through the same launchDaemonChild path. On a remote without lingering, that daemon used to go into a scope under the login-triggered user manager, which stops about 10 s after the last SSH session closes while orcad itself lives on in the session scope. Now it stays in the session scope next to orcad, and the one-line hint appears in the remote orcad.log (where orcad's stderr goes). On a remote with lingering on, nothing changes.
  • Agents and integrations: no agent-specific code. Agent terminals no longer die at an unrelated logout.
  • Performance: at most one small file read, one access and one getpwuid per daemon launch (an infrequent event); no polling. The warning is logged once per process.
  • UI: none.
  • Security: read-only checks of /proc/self/cgroup and of logind's linger record under /var/lib/systemd/linger/; the username comes from the OS, not from input, and is only C-escaped into a path join and used in a log line. No new privileges, no command execution added.
  • Behaviour change to note: a Linux desktop whose apps run in a login session-N.scope (not under user@<uid>.service) and without lingering now uses the direct-fork launch. The daemon there stays in the session scope, which with logind's default KillUserProcesses=no outlives the user manager at logout, so it is no less durable than before. With KillUserProcesses=yes the session scope is stopped at logout, so the daemon dies then, about 10 s sooner than the old user-manager scope did. That host also kills the desktop launcher at logout, so the terminals end with the app in both cases.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Not applicable: core daemon launch code, not the skill-installer system.

Notes

  • Security, cross-platform, remote SSH, mobile, backwards compatibility, performance: see Review. Mobile is unaffected. Backwards compatible: the new parameters are trailing and optional, and hosts with lingering on (the documented setup) behave exactly as before.
  • Operators on hosts that relied on an open login for the scope will now see the one-line hint and the direct-fork fallback until they run sudo loginctl enable-linger <account>, which the headless doc already asks for.
  • Possible overlap: fix(linux): repair disabled session bus in daemon and PTY environments #21120 exports resolveUserRuntimeDir from the same file; the two changes touch different functions and should rebase trivially.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

The last box is not ticked: the full commands did not fit in the local memory/runtime cap. The parts that cover this change pass (see Testing), and CI will run the full four commands.

🤖 Generated with Claude Code

@greptile-apps

greptile-apps Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Adds user lingering checks to daemon scope logic.

The PR appears safe to merge; no outstanding finding or new actionable regression was identified.

Summary

The PR prevents the terminal daemon from entering a user-manager scope that can disappear after the last logout.

  • The probe now accepts a lingering user or a launcher already under that user manager; otherwise it falls back to direct launch.
  • It distinguishes a missing linger record from an unknown linger state in its one-time warning, adds fixture-based tests, and updates the Linux operations guidance.

Reviews (2) · Last reviewed commit: "fix(daemon): only use the durable user s..."

Comment thread src/main/daemon/daemon-cgroup-scope.ts Outdated
Comment thread src/main/daemon/daemon-cgroup-scope.test.ts Outdated
@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: f13c7b63-5f94-4878-862a-665f80aa0bee

📥 Commits

Reviewing files that changed from the base of the PR and between c47f92f427a8b9dccf35e417fc1a15028c676c60 and bff26c4.

📒 Files selected for processing (5)
  • docs/reference/headless-linux-server.md
  • docs/reference/orcad-operations.md
  • src/main/daemon/daemon-cgroup-scope.test.ts
  • src/main/daemon/daemon-cgroup-scope.ts
  • src/main/daemon/systemd-linger-record.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/reference/headless-linux-server.md

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

The durable daemon-scope check now requires lingering or a launcher running under the matching user manager. If neither condition is met, it logs a one-time loginctl enable-linger hint and reports the scope as unsupported. The check fails closed when username or cgroup information is unavailable. Legacy-scope migration uses the updated check. Tests and reference documentation cover these requirements.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to bff26

The durable daemon scope is now used only when the user manager outlives login sessions. Otherwise the daemon falls back to direct fork and logs a one-time hint. No unresolved defects were found, so the change looks ready to merge once CI passes. A live systemd logout test would still add confidence.

Security Architecture Review

Security architecture risk: 🔵 Low · up to bff26

The change avoids relying on a temporary login session for terminal persistence and does not add privileges. The existing fallback remains available, but its terminals can still stop with the service. Already-running terminals are not automatically relocated when migration is rejected.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The lifecycle effect can encompass the daemon and all terminals it owns, rather than one terminal. The inspected change restricts an existing user-manager path without adding a new service-account, tenant, or privileged-manager target.

Trust Boundaries and Controls

  • observed — The own-manager check requires an exact user@UID.service path component. Linger lookup uses the OS username and rejects missing or indeterminate records. Bus selection still prefers the canonical UID-derived runtime directory before the existing environment fallback; that fallback is not a new authority path introduced here.

Resilience and Maintainability Implications

  • observed — Legacy migration reports success from command exit status and timeout state without verifying resulting PID cgroups. Repository evidence therefore does not resolve partial movement after interruption or an ambiguous result. This migration mechanism predates the added gate and is not established as a worsened security condition.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 3 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #24201. isDurableDaemonScopeSupported now requires a reachable user bus and accepts a durable scope only when the user has a confirmed linger record or the launcher runs un…
Out of Scope Changes check ✅ Passed The source changes, targeted tests, and documentation updates all support issue #24201. The linger-record helper and migration changes implement or verify the durable-scope eligibility behavior. No un…
Title check ✅ Passed The title clearly and concisely describes the main change: limiting durable user scopes to cases where the user manager persists.
Description check ✅ Passed The description follows the required template and provides complete ELI5, change, rationale, issue, visual proof, testing, AI disclosure, review, compatibility, and checklist details. It also explains…
Full details: Docstring Coverage

Explanation

Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 3 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 5a495fdb-49e8-44f2-836b-4c381a572e28

📥 Commits

Reviewing files that changed from the base of the PR and between cb36344 and c47f92f427a8b9dccf35e417fc1a15028c676c60.

📒 Files selected for processing (4)
  • docs/reference/headless-linux-server.md
  • docs/reference/orcad-operations.md
  • src/main/daemon/daemon-cgroup-scope.test.ts
  • src/main/daemon/daemon-cgroup-scope.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Comment thread src/main/daemon/daemon-cgroup-scope.ts Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ Minor suggestions only — two documentation-accuracy fixes in otherwise sound, well-tested code.

Reviewed changes

  • Durable-scope gate now requires a persistent user manager — isDurableDaemonScopeSupported() additionally checks, after the platform/boot/bus/systemd-run gates, that the user manager outlives the current sessions: lingering (/var/lib/systemd/linger/<user>) or the launcher's own cgroup containing user@<uid>.service. Otherwise it logs one enable-linger hint per process and returns false, so launchDaemonChild uses the existing direct-fork fallback.
  • New test seams threaded through migration — lingerDir / ownCgroupPath trail the existing seams and migrateLegacyDaemonScope forwards them; production callers pass nothing.
  • Tests — seven new cases in daemon-cgroup-scope.test.ts with hermetic fixtures (real Unix socket, temp linger/cgroup dirs); I ran the file and all 32 pass.
  • Docs — headless-linux-server.md and orcad-operations.md explain that an open login is not a substitute for lingering.

I independently verified the load-bearing systemd assumption (session cgroups are siblings of user@<uid>.service, not children) and found no functional defect. The behavior change for desktops in a session-N.scope is already documented in the PR body and degrades to the proven fallback.

ℹ️ Nitpicks

  • daemon-cgroup-scope.ts:126 still calls the probe "side-effect-free" in its JSDoc, but it now logs the once-per-process enable-linger hint. Worth updating the contract sentence so the logging isn't mistaken for a bug.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/main/daemon/daemon-cgroup-scope.ts Outdated
Comment thread docs/reference/orcad-operations.md Outdated
…rsists

The durable-scope probe accepted any reachable user bus. For `orca serve`
as a system unit with User= and lingering off, that bus only exists while
the account has a login session, and logind stops the user manager, with
the daemon's orca-daemon-<nonce>.scope and every terminal in it, seconds
after the last logout.

Also require that the manager outlives the current sessions: lingering is
on (logind's record in /var/lib/systemd/linger/, named by systemd's
cescape() of the user name), or the launcher already runs under
user@<uid>.service. Otherwise take the existing direct-fork fallback and
log a one-time `loginctl enable-linger` hint, or a "cannot tell" line when
there is no passwd entry or the record cannot be checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brynnclaw
brynnclaw force-pushed the fix/daemon-scope-requires-linger branch from c47f92f to bff26c4 Compare October 1, 2026 07:13
@brynnclaw

Copy link
Copy Markdown
Contributor Author

Re: pullfrog review, nitpick on daemon-cgroup-scope.ts:126

Thanks, good catch. In bff26c4 the JSDoc reads "Best-effort, read-only capability probe that logs at most one hint per process", and the PR description no longer says side-effect-free.

@brynnclaw

Copy link
Copy Markdown
Contributor Author

Re: coderabbit summary, pre-merge check "Docstring Coverage"

Thanks. The repo has no docstring-coverage rule (AGENTS.md asks for brief, non-obvious comments only), so we're not adding docstrings for the threshold. The JSDoc wording you'd expect to matter (side effects, the desktop case) is being corrected in the next push.

@brynnclaw

Copy link
Copy Markdown
Contributor Author

Re: coderabbit review, "Actionable comments posted: 1"

Thanks. The one actionable comment (logind's escaped linger file name) is fixed in bff26c4; details in the inline thread.

@brynnclaw

Copy link
Copy Markdown
Contributor Author

Re: greptile summary, findings 1 and 2

Thanks. Both findings are addressed in bff26c4. The hint no longer says lingering is off when it can't check (no passwd entry or an unreadable record). The test fixtures no longer read the host's passwd database. Details in the two inline threads.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes (delta since the prior pullfrog review at c47f92f)

  • Linger-record handling extracted into systemd-linger-record.ts — currentUsername() and isLingering() move out of daemon-cgroup-scope.ts, and a new lingerRecordName() reproduces systemd's cescape().
  • Probe now separates "lingering is off" from "cannot tell" — a missing record logs the loginctl enable-linger <user> hint, while an absent passwd entry or an unreadable linger dir logs a distinct "cannot tell whether lingering is on" line, once per process.
  • Prior doc feedback addressed — the runsUnderOwnUserManager docstring and the orcad-operations.md bullet now name the shapes that actually match (app-*.scope under user@<uid>.service, or a systemctl --user unit) and state a plain session-N.scope login does not count; the JSDoc no longer claims to be "side-effect-free".
  • Tests reworked and extended — the suite now mocks node:os at file level with a name seam, adds four cescape() fixture cases, an unreadable-dir case, and "cannot tell" assertions; existing fixtures inject a linger dir so they stay hermetic.

I independently verified the load-bearing external contract against systemd source: user_check_linger_file() (src/login/logind-user.c) reads /var/lib/systemd/linger/<cescape(user_name)>, method_set_user_linger() (src/login/logind-dbus.c) writes the same path from cescape(pw->pw_name), and cescape_char() (src/basic/escape.c) matches lingerRecordName byte-for-byte — including the named escapes and 3-digit octal range. isLingering's ENOENT→false / other-error→null mapping mirrors logind's access(p, F_OK). The new test fixtures (jos\303\251, DOMAIN\\orca, o\'brien, tab\there\"\177) are consistent with that contract, so the tests can fail if the escaping regresses. No functional defect found; the two prior threads are addressed and resolved.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

This branch has not been deployed

No deployments
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.

[Bug]: Linux headless: without lingering, the PTY daemon is placed in a user scope that is stopped at the last logout

1 participant