Skip to content

fix(access): apply 'as <name>' and save admins.json atomically - #197

Merged
Rushaway merged 3 commits into
masterfrom
fix/191-adminaccess-regname
Oct 14, 2026
Merged

Rushaway merged 3 commits into
masterfrom
fix/191-adminaccess-regname

Conversation

@Rushaway

Copy link
Copy Markdown
Member

!access <player> as <name> <level> now sets the name

AdminAccess._func checked if "Regname" in locals(), but the variable is reg_name, so the check was always False and the registered name was never applied. reg_name is now initialized to None and tested directly. When a name is given, targeted_player.admin.name is set and the existing "level/name" chat message is used. All level and permission checks are unchanged.

Empty target no longer raises IndexError

buffer[0] raised when the target part parsed to an empty string. The command now replies Missing target: use a player name or #userid in chat, like the other errors in this command, and returns 7.

Note: CommandHandler already strips message[1], so most inputs never reach this path. This is a guard for unusual whitespace, for example a tab or an ideographic space. !access as foo 5 still searches for a player whose name contains "as foo", as before.

admins.json is saved atomically

AccessManager.Save() used to truncate and rewrite admins.json in place. If the process crashed, was killed or ran out of disk mid-write, the file was left truncated, and since #171 a malformed admins.json aborts startup. Save() now:

  • writes to a temp file in the same directory (tempfile.mkstemp), then flushes and fsyncs it;
  • copies the mode of the existing file, because mkstemp creates files as 0600;
  • swaps it in with os.replace;
  • removes the temp file if anything fails, and the original stays untouched.

The JSON output (indent="\t", level-descending sort, text-mode newlines) is byte-identical to before.

Readability

In set_admin, the loop variable shadowed the admin parameter. It is now named existing_admin.

Test plan

I ran a throwaway script against a temp copy of config/, driving AdminAccess._func with stub torchlight and player-manager objects:

  • !access Target as NewName 5 returns 0, sets admin.name = "NewName" and level 5, and the access manager stores the new name.
  • !access #20 6 returns 0 and keeps the name. Assigning a level at or above the caller's own still returns 4.
  • An empty parsed target ("\t as foo 5", "  5") returns 7 with the chat message.
  • Save() output is byte-identical to the old open(..., "w") + json.dump for the same data.
  • A failure during json.dump, and an os.replace failure, both leave the original admins.json byte-identical with no .tmp file left behind.
  • ruff check ., ruff format --check . and mypy show no new errors. Only the 3 known baseline errors remain, and CI: Python Linting is red on master since the Python 3.12 bump (ruff UP041, mypy arg-type) #178 fixes them.

No VERSION bump (batch policy, see #177).

Closes #191
Part of #177

🤖 Generated with Claude Code

- AdminAccess: initialize reg_name and test it directly instead of the
  misspelled `"Regname" in locals()` check, so `!access <player> as <name>
  <level>` now sets the registered name. Level/permission checks unchanged.
- AdminAccess: reject an empty target with a chat message (return 7)
  instead of raising IndexError on buffer[0].
- AccessManager.Save(): write to a temp file in the config directory,
  flush + fsync, keep the original file mode, then os.replace(); the temp
  file is removed on failure. JSON formatting and sorting are unchanged.
- AccessManager.set_admin(): rename the loop variable that shadowed the
  `admin` parameter.

Closes #191

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Rushaway
Rushaway merged commit 833ba13 into master Oct 14, 2026
2 checks passed
@Rushaway
Rushaway deleted the fix/191-adminaccess-regname branch October 14, 2026 11:43
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.

AdminAccess: 'as <name>' is never applied; AccessManager.Save() writes admins.json non-atomically

1 participant