Allowatron is a local Codex PermissionRequest plugin for recurring Bash
approvals. It keeps Codex's normal approval UI for anything it cannot prove
from an explicit rule, and records those deferred requests in a local review
queue.
The v0.3 implementation is deterministic and offline. It does not call a GPT model, execute pending commands, replace Codex's client, or change Codex's native permission rules.
The plugin uses Python 3.11+ and the pinned regex and interactive questionary
dependencies in requirements.txt. The hook never installs packages or accesses the network.
If the host runtime lacks the pinned regex dependency, the hook defers and
reports a repair command. The Questionary dependency is only needed for the
interactive review widget; --plain remains available without it.
Run the management CLI from the plugin checkout (quote the path if it contains spaces):
python3 "/path/to/allowatron/scripts/allowatron.py" doctor --json
python3 "/path/to/allowatron/scripts/bootstrap_runtime.py" \
--data-dir "$PLUGIN_DATA"The bootstrap command is explicit and creates a mutable virtualenv under the
selected data directory. It may download the pinned package; do not run it from
the hook. When that virtualenv exists, the quoted hook and CLI launchers use it;
they never create or update it automatically. The installed hook receives
PLUGIN_DATA from Codex and the CLI accepts --data-dir so both use the same
store. Without either, the CLI uses
~/.codex/plugins/data/allowatron; there is no checkout-relative fallback.
For the personal marketplace workflow:
codex plugin add allowatron@personalCodex may require the PermissionRequest hook to be reviewed/trusted separately from plugin installation. Start a new Codex thread after an update.
Register project roots explicitly, then import a rule document. A checked-out JSON file is inert until it is imported:
allowatron() {
python3 "/path/to/allowatron/scripts/allowatron.py" \
--data-dir "$PLUGIN_DATA" "$@"
}
allowatron project add /absolute/path/to/project
allowatron rules validate examples/rules.json
allowatron rules import examples/rules.json
allowatron rules listThe function keeps plugin paths with spaces safely quoted.
Rule documents are versioned JSON. Each rule has an ID, description, decision,
global/project/session scope, executable, and an ordered argument list. Argument
matchers are literal, glob (* and ?, neither crosses /), or full-string
regex:
{
"version": 1,
"rules": [
{
"id": "read-pr-comments",
"enabled": true,
"description": "Read PR comments in one project",
"decision": "allow",
"scope": { "project": "/absolute/path/to/project" },
"command": {
"executable": "gh",
"arguments": [
{ "literal": "api" },
{ "regex": "repos/acme/app/pulls/[0-9]+/comments" },
{ "literal": "--paginate" }
]
}
}
]
}Rules compare the complete argv: executable, argument order, argument count,
and scope must all match. Across matching enabled rules, deny wins over
defer, which wins over allow. A disabled rule does not match.
Scopes may be project-wide ({"project": "/absolute/path/to/project"}),
session-specific ({"project": "/absolute/path/to/project", "session_id": "..."}),
or global ({"global": true}). Global rules apply to all sessions in every
registered project; they do not authorize commands from unregistered directories.
When no rule matches, the hook returns {}. Codex retains its ordinary prompt
behavior and Allowatron records a sanitized, deduplicated queue entry for
commands that can be represented by the rule model:
python3 "/path/to/allowatron/scripts/allowatron.py" \
--data-dir "$PLUGIN_DATA" queue list
python3 "/path/to/allowatron/scripts/allowatron.py" \
--data-dir "$PLUGIN_DATA" review 12review opens a newest-first pending queue when no ID is supplied. In a real
terminal it uses an inline arrow-key/shortcut selector with Back at every menu
and Ctrl+B at text prompts; it keeps ordinary terminal scrollback rather than
using a full-screen interface. Select a request, then choose allow/deny/defer,
project/session/global scope, exact literals or customized matchers for one or
more arguments, preview positive and negative examples against the real
evaluator, and make an explicit final save choice. The matcher editor lists
every positional argument and supports revisiting, replacing, or resetting an
argument matcher before selecting Done. Use review ID --plain for
numbered/text prompts,
or when stdin/stdout is not a TTY. The rule is saved before the queue entry is
marked resolved. EOF, cancel, invalid patterns, concurrent changes, or a failed
write leave the entry pending.
If a pending request is close to an existing same-shape rule, review lists the
nearby rule and offers editing it or creating a new rule. Editing an existing
rule starts from its saved matchers; Reset restores the saved matcher rather
than treating a regex or glob as a literal. A positive example is required and
the preview shows original/proposed rules, changed fields, example results, and
effective precedence. The queued command may be used as a safe example when it
was not redacted. Saving never resolves a request unless the user explicitly
chooses save-and-resolve and the fresh effective result is allow or deny.
To diagnose a prompt without changing anything, use explain:
allowatron explain --project /absolute/path/to/project \
--command 'git commit -m subject -m body' --json
allowatron explain --project /absolute/path/to/project \
--command 'git commit -m subject -m body' --rule commit-ruleWhen no rule matches, the output identifies up to five nearby enabled rules and
the exact argument, count, executable, or scope cause. JSON adds near_misses
and diagnostic completeness metadata. These suggestions are navigation aids;
they never authorize a command or alter the evaluator's decision.
Commands with unsupported shell syntax or unsupported wrappers still defer to
Codex, but are not added to the review queue by default because no Allowatron
rule can match them. Set ALLOWATRON_QUEUE_UNSUPPORTED=1 before hook use to
retain those requests for audit; the review UI treats them as dismiss-only.
Useful management commands:
allowatron doctor [--json]
allowatron project add|list|remove PATH
allowatron rules list|validate|import|export|preview|upsert|disable|remove
allowatron explain --project PATH [--session ID] --command '...' [--rule ID]
allowatron queue list|show|dismiss|resolve|prune [--all]
allowatron review [ENTRY_ID] [--rule ID] [--plain]
See the complete command reference for command options, JSON formats, rule matching, queue lifecycle, and safety boundaries.
explain is read-only: it never executes a command, queues a request, or
changes active rules. --json is available on read-only listing, validation,
export, explain, doctor, queue inspection, preview, and upsert commands.
The plugin includes an allowatron skill for inspecting and tuning rules from
within a Codex conversation. It uses the same CLI, evaluator, and data store as
the hook. The skill can list deferred requests, explain a decision, preview a
single-rule draft with positive and negative examples, and apply an explicitly
authorized guarded update. It does not run queued commands or use the
interactive terminal review flow.
Queue inspection uses a read-only SQLite connection so the skill can inspect a Codex data directory when its sandbox mounts that directory read-only. Rule activation and queue resolution still require write access to the selected store; the skill must report a sandbox write failure rather than creating a second store.
For a conversational edit, the skill keeps a draft file separate from active
rules, previews the full argument match and scope, then uses rules upsert only
after authorization. Same-ID edits include the current expected_rule, so a
concurrent change is rejected instead of overwriting it. The terminal
review --rule ID editor provides the same guarded saved-rule workflow, while
pending review can optionally edit a nearby rule or create a new one. If the
installed marketplace data directory cannot be identified, the skill requires
an explicit ALLOWATRON_DATA_DIR or --data-dir.
The parser accepts only one simple command made from literal words, spaces/tabs,
and single/double quotes. It rejects shell operators, redirects, substitutions,
backslashes, comments, glob/brace/tilde expansions, control characters,
leading assignments, and wrappers such as env, sudo, and sh -c. These are
deliberate false negatives: the request is deferred rather than interpreted as
safe. Allowatron never runs a command to inspect it and never resolves an
executable through PATH.
Project roots are registered explicitly and resolved by path components using the longest registered ancestor. Sibling names cannot match by string prefix; distinct worktrees are distinct scopes. Session rules require the exact project/session pair. Global rules still require a registered project context.
Queue data lives in queue.db with a private HMAC key, mode 0700 data
directories, and mode 0600 files. It retains entries for at most 7 days and
100 records by default; the same limits are enforced when new requests are
recorded. Use allowatron queue prune --all to explicitly delete pending,
resolved, and dismissed entries. Common token/password flags, authorization
headers, and URL credentials are redacted; arbitrary secrets can evade
best-effort redaction.
Set ALLOWATRON_METADATA_ONLY=1 before queue use to store no command/argv
content. Redacted or metadata-only entries require a clean user-supplied
example before promotion to a rule.
The old feasibility review.jsonl hash-only file is not imported or promoted.
Deleting the data directory removes active rules, registered projects, queue
history, and the fingerprint key; uninstalling the plugin does not delete it.
Run the offline verification suite from the repository root:
python3 -X tracemalloc=10 -W error::ResourceWarning \
-m unittest discover -s tests -vValidate the plugin manifest with the plugin-creator validator, and measure local hook latency with synthetic state:
python3 /path/to/plugin-creator/scripts/validate_plugin.py .
python3 scripts/benchmark.py --rules 100 --queue 1000 --iterations 100The benchmark reports the machine and sample count only when you run it; its target is p95 below 200 ms locally, with evaluation bounded below Codex's hook timeout. No live trust or prompt behavior is inferred from these automated checks; verify those in a fresh Codex thread after installation.
