Skip to content

fix(settings): restart the settings daemon in one step - #386

Merged
lionello merged 1 commit into
masterfrom
fix/settings-rev-stale-after-switch
Aug 26, 2026
Merged

fix(settings): restart the settings daemon in one step#386
lionello merged 1 commit into
masterfrom
fix/settings-rev-stale-after-switch

Conversation

@defangdevs

Copy link
Copy Markdown
Owner

The symptom

The settings page reported an update that was already applied:

Currently at dcc9a2a8dc7e. agent-box update available — 40 commits.

Meanwhile agent-box-update.service said already current and refused to rebuild. Both pins were at 79e5222. An operator has no way to tell which of the two is right.

The cause

The page renders AGENT_BOX_REV, which the daemon reads from its environment once at startup (render_update_line(), modules/src/settings-daemon.py). On the deployed box the live process still held generation 27's rev while systemd's unit definition, the pin file and the update unit all carried generation 28's:

Source Rev
Running daemon (/proc/<pid>/environ) dcc9a2a8… (gen 27, #357)
Gen 28 drop-in, systemctl show, /etc/nixos/agent-box-pin.nix 79e52224… (#381)

dcc9a2a8..master is exactly 40 commits, which is the number the card printed. 79e5222..master was 0.

The unit is socket-activated (Requires=agent-box-settings@%i.socket). NixOS's default stopIfChanged = true stops it in the old configuration, before the new one's daemon-reload. A client holding the settings page open reconnects into that window, the socket starts the daemon again from systemd's cached old unit definition, and switch-to-configuration's later start step finds it already running.

Start timestamps on the box support this: the settings daemon came up at 17:47:36, one second before agent-box@agent and agent-box-webhook@agent at 17:47:37. Neither of those is socket-activated, and both picked up the new definition correctly (the receiver reports 0.23.0, this PR's parent #381).

The fix

stopIfChanged = false on the settings drop-in makes it a single systemctl restart, issued after the new definition is loaded. Rendered result, the whole behavior change:

--- a/tests/golden/web/units/agent-box-settings@agent.service
+++ b/tests/golden/web/units/agent-box-settings@agent.service
+X-StopIfChanged=false

This does not make the rev dynamic — the daemon still reads it at startup. It makes the restart reliable, so startup happens under the new configuration. Moving the rev to a file read per request would be the thorough fix; it is not needed if the restart is correct.

Verification

Regenerated with nix run .#assemble and nix run .#update-golden. Checks run natively on aarch64-linux:

Check Result
module-generated-up-to-date ok
golden-snapshot ok
assemble-module-escaping ok
multi-user ok
module-single-file ok

settings-page, golden-snapshot and memory-protection for x86_64-linux evaluate cleanly (.drvPath); the VM runs themselves need an x86 host, so CI covers those.

Only the web golden profile changed. The vm profile has web.enable off, so it ships no settings unit.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PsqGhkpjsb4kKAdVz25xKN

The settings page renders the running rev from AGENT_BOX_REV, which the
daemon reads from its environment once at startup. After a rebuild the
card can therefore advertise an update that is already applied.

Observed on the deployed box: generation 28 carried rev 79e5222, but the
live daemon still held generation 27's dcc9a2a and reported "agent-box
update available - 40 commits" (exactly dcc9a2a..master). The update
service itself said "already current" and did not rebuild, so the two
disagreed with no way for the operator to tell which was right.

The unit is socket-activated. NixOS's default two-step restart stops it
in the OLD configuration, before the new one's daemon-reload, so a
client holding the settings page open reconnects into that window; the
socket restarts the daemon from systemd's cached old unit definition and
switch-to-configuration's later start step finds it already running. The
timestamps match: the settings daemon came up at 17:47:36, one second
BEFORE the supervisor and the webhook receiver, which are not
socket-activated and picked up the new definition correctly.

stopIfChanged = false makes it a single `systemctl restart` after the
new definition is loaded, which closes the window.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsqGhkpjsb4kKAdVz25xKN
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 02ccf490-4e49-4666-813c-94cbffb9aa9e

📥 Commits

Reviewing files that changed from the base of the PR and between 3e78487 and 6b1eb9e.

📒 Files selected for processing (3)
  • modules/agent-box.nix
  • modules/agent-box.nix.in
  • tests/golden/web/units/agent-box-settings@agent.service

Included review availability: Your plan provides up to 10 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

The systemd settings service definitions now disable stop-on-change behavior. The golden service fixture includes the generated X-StopIfChanged=false property.

Changes

Settings daemon lifecycle

Layer / File(s) Summary
Disable stop-on-change behavior
modules/agent-box.nix, modules/agent-box.nix.in, tests/golden/web/units/agent-box-settings@agent.service
The service definitions set stopIfChanged = false. The golden service unit records X-StopIfChanged=false.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 6b1eb

This localized change makes the settings daemon restart under the updated configuration, preventing stale settings from being shown after an update. The listed checks pass, and no actionable merge-blocking risk remains.

Suggested reviewers: lionello, claude, codex

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: restarting the settings daemon in one step to prevent stale configuration.
Description check ✅ Passed The description directly explains the stale revision symptom, the socket-activation cause, the stopIfChanged fix, and verification results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0 files. (3 skipped: 3 unsupported.)

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/settings-rev-stale-after-switch

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

@lionello
lionello merged commit 770943f into master Aug 26, 2026
2 checks passed
@lionello
lionello deleted the fix/settings-rev-stale-after-switch branch August 26, 2026 20:37
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Agent-Box Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants