Problem
/etc/agent-box-guides/AGENTS.<user>.md tells the agent:
agentbox apply --dry-run prints what the box's declared configuration would change without changing it - the fastest way to see how this host is actually put together.
An agent is not root, and --dry-run dies on the first file it cannot read:
$ python3 bin/agentbox apply --dry-run --config tests/native/config.json \
--profile /nix/var/nix/profiles/agent-box
would write /etc/agent-box/units/robot.env
Traceback (most recent call last):
...
File "bin/agentbox", line 3157, in commit
if write_if_changed(path, text, mode, args.dry_run):
File "bin/agentbox", line 2748, in write_if_changed
current = path.read_text()
PermissionError: [Errno 13] Permission denied: '/etc/agent-box/web-users'
write_if_changed reads the current file to decide whether anything differs, and only FileNotFoundError is caught:
try:
current = path.read_text()
same = current == text and (path.stat().st_mode & 0o7777) == mode
except FileNotFoundError:
same = False
/etc/agent-box/web-users is 0600 root, so the read raises PermissionError and the run ends mid-way through the file list. Nothing was going to be written -- the crash is entirely in the comparison a dry run does not need to be able to make.
Impact
Read-only inspection of a native box is unavailable to the agent living on it, and the failure looks like a broken renderer rather than a permission the caller does not have. Also true of a partial --dry-run as root on a box where an administrator made one of our files unreadable.
Not a live-apply bug: an apply that runs as root reads these files fine.
Suggested fix
Treat an unreadable file as "differs" rather than as fatal -- catch OSError (and UnicodeDecodeError, which generated_by_us already handles for the same reason) around the comparison:
except (OSError, UnicodeDecodeError):
same = False
A dry run then reports would write /etc/agent-box/web-users, which is the honest answer: it cannot prove the file already matches. A real apply as root is unaffected, since it can read the file.
The remaining question is whether a non-root --dry-run should say so up front -- the file list it prints is a superset of what a root run would change, because everything it could not read reads as changed. A one-line note when os.geteuid() != 0 would keep that from being mistaken for drift.
Notes
Spotted while working #526 (PR #539), which is where the run above came from; unrelated to that change and present before it.
Problem
/etc/agent-box-guides/AGENTS.<user>.mdtells the agent:An agent is not root, and
--dry-rundies on the first file it cannot read:write_if_changedreads the current file to decide whether anything differs, and onlyFileNotFoundErroris caught:/etc/agent-box/web-usersis 0600 root, so the read raisesPermissionErrorand the run ends mid-way through the file list. Nothing was going to be written -- the crash is entirely in the comparison a dry run does not need to be able to make.Impact
Read-only inspection of a native box is unavailable to the agent living on it, and the failure looks like a broken renderer rather than a permission the caller does not have. Also true of a partial
--dry-runas root on a box where an administrator made one of our files unreadable.Not a live-apply bug: an apply that runs as root reads these files fine.
Suggested fix
Treat an unreadable file as "differs" rather than as fatal -- catch
OSError(andUnicodeDecodeError, whichgenerated_by_usalready handles for the same reason) around the comparison:A dry run then reports
would write /etc/agent-box/web-users, which is the honest answer: it cannot prove the file already matches. A real apply as root is unaffected, since it can read the file.The remaining question is whether a non-root
--dry-runshould say so up front -- the file list it prints is a superset of what a root run would change, because everything it could not read reads as changed. A one-line note whenos.geteuid() != 0would keep that from being mistaken for drift.Notes
Spotted while working #526 (PR #539), which is where the run above came from; unrelated to that change and present before it.