Skip to content

fix(nix): NixOS module lingers the service user so restart-safe cron workers get a user scope (#110628, salvage #110629) - #111489

Merged
teknium1 merged 3 commits into
mainfrom
fix/cronrt-nixos-linger
Sep 15, 2026
Merged

teknium1 merged 3 commits into
mainfrom
fix/cronrt-nixos-linger

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

On NixOS, services.hermes-agent.enable = true now gives the service uid a systemd user manager (linger), so gateway-dispatched cron jobs can create their restart-safe systemd-run --user --scope instead of running unscoped or, under cron.require_restart_safe_scope, failing every fire.

Fixes #110628

  • nix/nixosModules.nix: users.users.<user>.linger = lib.mkDefault (!cfg.container.enable) under createUser; the native hermes-agent unit is ordered after/wants linger-users.service and gets a bounded, non-fatal preStart wait (≤10 s) for /run/user/<uid>/bus — loginctl enable-linger returns before user@<uid>.service is up, and run_gateway adopts the bus env exactly once at startup. All gated on lingerEnabled read back off config, so createUser = false never blocks on a bus that may never arrive.
  • nix/checks.nix: cron-worker-user-scope evaluates the module and asserts the four properties (linger set, ordering, preStart wait present, no wait for an unmanaged uid).
  • Docs: website/docs/getting-started/nix-setup.md gets the linger/cron note including the createUser = false remedy.

Root cause: a system unit (User=hermes, isSystemUser = true) is neither a login session nor a lingering user, so logind never starts user@995.service and there is no bus for systemd-run --user. The module never applied the remedy the runtime error names.

Premise vs current main: on origin/main the default path no longer fails closed — restart_safe_gateway_child_argv degrades to a direct external subprocess (GatewayChildDispatch("degraded"), landed in 560b6d2e8 / v2026.9.14) and only cron.require_restart_safe_scope: true raises. The module half is still needed for restart-safe workers to actually exist on a stock NixOS install. The /bin/true probe payload half (haizaar's follow-up comment) landed in #105365.

Live verification: no nix on this host — static check only. git diff of the salvaged commit re-read line by line against the module (createUser block, MODE A native unit); the runtime default was probed in Python: restart_safe_gateway_child_argv([...], require_restart_safe_scope=False) with the probe forced False → mode == "degraded"; True → the descriptive RuntimeError. The nix flake check job in CI (.github/workflows/nix.yml) runs the new cron-worker-user-scope check.

Related: #110803 (same failure class, a stale True probe on a non-Nix host) is fixed separately.

Credit

Salvages #110629 by @haizaar (cherry-picked with authorship). #110641 by @liuhao1024 proposed the same linger line gated on !container.enable; that gate is carried here in the follow-up commit.

Infographic

nixos-linger

haizaar and others added 3 commits September 14, 2026 19:00
The NixOS module produces the one topology the restart-safe cron worker
cannot run in: a system service (`User=hermes`, `isSystemUser = true`)
whose uid has no systemd user manager. `run_one_job` hands every fire to
`_launch_external_cron_worker` → `restart_safe_gateway_child_argv`, which
requires a transient `systemd-run --user --scope` and fails closed when it
cannot get one:

  Restart-safe cron worker dispatch failed: cannot create restart-safe
  systemd scope for gateway child: systemd-run --user --scope is
  unavailable (usually no reachable user D-Bus session at
  /run/user/995/bus). On a system-level service install, run
  `sudo loginctl enable-linger <gateway-user>` and restart the gateway.

So on a stock `services.hermes-agent.enable = true` host, no cron job runs
at all — every fire is recorded as a failed execution before an agent
starts, and the occurrence is consumed, so a weekly job does not retry for
another week. The error names the remedy, but nothing in the module applies
it, and the terminal-worker path with the same cgroup-isolation goal
degrades gracefully instead (`process_registry.py`: "worker shares the
gateway cgroup"), so nothing else on the host looks wrong.

Three parts, because linger alone leaves a startup race:

- `linger = lib.mkDefault true` on the created user: the declarative
  `loginctl enable-linger`, which gives the uid a user manager and with it
  /run/user/<uid>/bus. mkDefault, so an operator can still refuse it.
- after/wants linger-users.service, the unit that runs enable-linger.
- a bounded preStart wait for the socket. logind starts user@<uid>.service
  asynchronously, and `run_gateway` resolves XDG_RUNTIME_DIR /
  DBUS_SESSION_BUS_ADDRESS exactly once at startup (802f0f9), so a bus that
  appears after ExecStart is one the process never sees for its lifetime.
  Non-fatal after 10s: a gateway without cron beats no gateway.

All three are gated on `lingerEnabled`, read back off `config` rather than
assumed: with `createUser = false` the operator owns the user, nothing here
knows whether they lingered it, and the unit must not block on a bus that
may never arrive.

Test: nix/checks.nix `cron-worker-user-scope` relates the unit's `User=` to
that user's `linger`, requires the ordering and the wait when it lingers,
and requires neither when the module does not own the user. Red on the
parent commit for the first three arms.

Needs nixpkgs >= 25.05 for `users.manageLingering`.
…ent the cron/linger dependency

The scope-dispatching cron worker runs inside the container there, so a
host user manager would start for nothing (as PR #110641 by @liuhao1024
also gated it). nix-setup.md gains the note operators need when they
declare the user themselves.
@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

ran on 73d27d7 — chore: map contributor email for @haizaar

⚠️ Warnings

OSV vulnerability scan · View job

76 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


debug info

CI timings

CI timings · View report · View job

Wall time 6m39s vs 5m28s (+21.6%). 8 job(s) slower, 4 faster, 3 unchanged.

  • Docs Site / docs-site-checks: +52.0s
  • Python tests / Run tests: -24.0s
  • OS-specific tests / macOS-only tests: +17.0s
  • OS-specific tests / Windows-only tests: +10.0s
  • OSV scan / Emit review status: -6.0s

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists area/nix Nix flake, NixOS module, container packaging comp/cron Cron scheduler and job management labels Sep 15, 2026
@teknium1
teknium1 merged commit d47ac8d into main Sep 15, 2026
39 checks passed
@teknium1
teknium1 deleted the fix/cronrt-nixos-linger branch September 15, 2026 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/nix Nix flake, NixOS module, container packaging comp/cron Cron scheduler and job management P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: NixOS module — cron never runs on a system service (restart-safe worker cannot create its user scope)

3 participants