Closed feature request

Auto mode classifier blocks adom CLIs: ship an allowlist from pkg install + the core hook

John Lauer · 2mo ago ·edited ·closed by Colby Knox

adom-wiki 1.7.3 · adom/core 4.13.13 · Claude Code 2.1.233 · Linux container (Windows 11 / WSL2 host)

Summary

Claude Code's auto mode now refuses commands using a model classifier, not just the static allow/deny rules in settings.json. Today it refused adom-wiki pkg publish on a machine with zero deny rules configured. Every Adom CLI is designed to be driven by an AI with no human in the loop, so this gate will keep stalling unattended flows until we ship an allowlist. Nothing in Adom writes permission rules today.

What happened

Publishing a skillpack from a normal checkout:

cd <checkout> && adom-wiki pkg publish

Verbatim refusal:

Permission for this action was denied by the Claude Code auto mode classifier.
Reason: Blocked by classifier. ... To allow this type of action in the future,
the user can add a Bash permission rule to their settings.

There was no matching deny rule. permissions was absent from ~/.claude/settings.json entirely.

Confirmed fix

Adding one rule and rerunning the identical command succeeded:

{ "permissions": { "allow": ["Bash(adom-wiki:*)"] } }

So an explicit allow rule takes precedence over the classifier. That is the lever we need, and it is the long-standing settings mechanism, not a new API.

Why this needs a platform fix, not per-user config

  1. Behavior can change without any user config changing, because the judgment lives in a model. A flow that has run silently for months can start refusing after a Claude Code update.
  2. It is not fully deterministic. Same command, different context, potentially a different verdict, so retrying is not a fix.
  3. It bites hardest exactly where Adom is strongest: unattended agent runs, cron/loop flows, and anything a bridge or app invokes on the user's behalf. The user may not be watching when it stalls.

Requests

1. adom-wiki pkg install should register the package's own CLI (primary ask). Add a bin field to package.json; on install, merge Bash(<bin>:*) into permissions.allow in ~/.claude/settings.json; on uninstall, remove it. One implementation in the CLI and every published package inherits it, with the allowlist scoped to binaries the user actually chose to install. The alternative, every package hand-rolling a JSON merge in its own install.sh, guarantees drift across 100+ packages.

2. Self-heal a baseline set through the adom-core-update.sh hook (fastest fleet-wide path). That hook already runs on UserPromptSubmit for every Adom user under both Claude Code and Codex, and already self-heals skills. An idempotent "ensure these allow rules exist" step would cover the fleet within one throttle window and repair drift automatically, with no image rebuild and no user action. Baseline: adom-wiki, adom-cli, adom-bridge, adom-google. This part lives in adom/core rather than the wiki CLI; happy for it to be split or transferred.

3. Optional, slower lane: /etc/claude-code/managed-settings.json in the golden image. Strongest guarantee since users cannot lose it, but it is enterprise policy that cannot be overridden, so it suits only the handful of Adom-owned binaries we are certain about. Needs an image rebuild. Not currently present on this container.

Scope: no prompts, full surface

To be unambiguous about what is being asked for: nothing managed by adom-wiki should prompt. Every Adom app is built to be driven by an AI, so a gate that stops an agent mid-task on a tool designed for agents is a defect, not a safety feature. That means the whole CLI surface including deprecate, uninstall, and dist-tag, and it means every per-package bin rule that request 1 generates. No carve-outs, no "everyday verbs only" split.

The residual risk is real: prompt-injected content in a page, repo, or email that an agent reads could drive a destructive verb. But an approval prompt is a weak control against exactly that case, because the user is usually away during unattended runs, and when present they click through. The mitigations belong where they actually work:

  • Reversibility at the registry. Publishes are versioned and deprecate is already reversible; make restore and un-deprecate first-class and documented, so a bad call is a two-second undo.
  • An audit trail of package mutations, attributed per user and per AI thread. Adom Bridge already does this with aiThread on every call; the same idea applied to pkg publish/deprecate/uninstall gives forensics without blocking anyone.
  • The non-destructive updater described in the companion bug report (publish drops unregistered skills, then update deletes the local copy).

Undo plus attribution beats a prompt nobody reads.

Note on this report

Details of how auto mode classifies are moving; the evidence above is what was observed on this machine today (2026-08-19), not a claim about documented behavior. Worth confirming against current Claude Code docs before implementing.

4 Replies

John Lauer · 2mo ago

Amended the "Scope" section after posting, in case you read the first version.

The original draft suggested allowing the everyday verbs and leaving deprecate / uninstall / dist-tag to prompt. Scratch that. The ask is no prompts for anything managed by adom-wiki, the full CLI surface plus every per-package bin rule that request 1 generates.

Reasoning is in the updated section: these apps are built to be AI-driven, an approval prompt does not actually stop prompt injection (the user is usually away, and clicks through when present), and the effective controls are reversibility at the registry plus a per-thread audit trail of package mutations. Undo plus attribution beats a prompt nobody reads.

Colby Knox · 2mo ago

Request 1 is shipped in adom-wiki 1.7.4, and it reaches your request-2 goal without touching adom/core:

bin -> allowlist, with uninstall symmetry. Declare bin in package.json (a bare command name, or an array). pkg install merges Bash(<bin>:*) into ~/.claude/settings.json permissions.allow (atomic write, preserves everything else in the file), records the rules in the installed ledger, and pkg uninstall removes exactly those rules, never one the user added. Verified live end to end, including idempotence (no duplicate rules, no repeat hints).

Fleet self-heal with no core change. The same-version backfill (the path the core hook's throttled pkg update already exercises for skills) also ensures the rules. adom-wiki declares bin: "adom-wiki" as of 1.7.4, so Bash(adom-wiki:*) lands fleet-wide in TWO hook windows: the first throttled update installs 1.7.4 (registration runs in the installing CLI, so the old binary can't do it), and the next update, now run by 1.7.4, registers the rule via the same-version self-heal. Verified live on this container in exactly that sequence, third run idempotent. Your request 2's outcome, delivered through request 1's mechanism. For the rest of your baseline (adom-cli, adom-bridge, adom-google), each package adds its one-line bin and inherits everything; no hand-rolled JSON merges in install.sh, which was exactly the drift you predicted.

Containment, since the allowlist is security-relevant. A bin must be a bare command name; paths, spaces, globs, and option-like strings are refused (BIN_REJECTED warns), and the rule body is always Bash(<name>:*), so a manifest cannot smuggle Bash(rm -rf:*) or a path rule into your settings. An unparseable settings.json is never clobbered (PERMISSIONS_SETTINGS_UNTOUCHED). Honest caveat: a package CAN declare a bin name it does not own (nothing verifies the name against the payload), but a hostile package already runs arbitrary install.sh at install time, so the allowlist adds no capability an installer lacks; the guard is against accidents and rule-body injection, not a substitute for trusting what you install.

On scope ("no prompts, full surface"): agreed and implemented as asked; Bash(adom-wiki:*) covers every verb including deprecate/uninstall/dist-tag. The real guards remain the CLI's own --confirm flags on destructive verbs, registry reversibility, and per-thread attribution: undo plus audit beats a prompt nobody reads.

Your request 3 (managed-settings in the golden image) is an infra call above this CLI: leaving that to Colby. This issue is otherwise done from the CLI side.

Colby Knox · 1mo ago

The catalog-wide half is now live on prod, closing the gap between "the mechanism exists" (1.7.4) and "the fleet benefits":

Manifests are served through a derivation step: when a package declares no bin, the registry derives one from the page's declared install binary (app_metadata.binary_name, the install.binary_name publish field) and serves it as bin with bin_derived: true. The CLI's existing 1.7.4 registration consumes it unchanged, so every package whose command was declared ANYWHERE now registers its allowlist rule on the next install or hook-window update, past releases included (serve-time enrichment, immutable tarballs untouched). Explicit bin always wins; bin: false opts out; derived names pass the same bare-command containment the CLI enforces, and there is deliberately NO slug-guessing: a guessed shell grant is worse than a prompt.

Verified on prod: adom/adom-parts-search and adom/chip-fetcher now serve derived bins. Packages that never declared a command anywhere (hero-studio, wiki-issue-loop install via install.sh symlinks with no binary_name) still do not derive; their fix is one line of page.json (install.binary_name) or package.json (bin), and I will sweep the adom-owned ones.

Remaining open here is only your amended scope question (item 2's ownership: whether the core hook should also seed a baseline beyond what installs register), which is Colby's call and the reason this stays open.

Colby Knox · 1mo ago

Closing with the full arc resolved:

Request 1 (primary): shipped in adom-wiki 1.7.4. package.json bin -> Bash(:*) in ~/.claude/settings.json permissions.allow, atomic writes, ledger symmetry on uninstall, bare-command containment guard, unparseable settings never touched.

Request 2 (fleet coverage): achieved without touching adom/core, in two layers. The same-version backfill registers rules through the core hook's existing throttled pkg update (adom-wiki itself landed fleet-wide this way), and the registry's serve-time derivation (live on prod, this thread's 2026-08-25 comment) extends it to every package whose install binary was declared anywhere, past releases included. The CLI consumed derivation with zero changes; a cosmetic follow-up rides the next CLI release (the confirmation hint names derived provenance, and BIN_REJECTED plus the skill teach "bin": false as the opt-out).

Request 2's remaining half, hook-seeded baseline rules beyond what installs register: declining. Install-scoped registration plus derivation now covers every real case, and it keeps grants scoped to binaries the user actually chose to install, which is this issue's own stated principle. A hook-seeded rule for a binary that is not installed is an unscoped grant with no owner; if a package is missing from the fleet allowlist the fix is its one-line bin or install.binary_name, not a parallel seeding path.

Request 3 (managed-settings golden image): not pursuing. Unnecessary now, and unoverridable enterprise policy is the wrong default for rules the install lifecycle already manages. It stays available as an escalation if the classifier ever outruns this mechanism.

Scope: your amended no-carve-outs ask is satisfied structurally. The rule is binary-scoped, so one Bash(adom-wiki:*) covers the entire surface including deprecate, uninstall, and dist-tag. On the mitigations that make that stance safe: un-deprecate is already first-class (pkg deprecate with an empty message) and delete is reversible (repo undelete, taught in the delete response). The per-AI-thread audit trail of package mutations is real work and bigger than this issue; if you want it tracked, file it fresh and it will get the same treatment.

Follow-through, not a blocker: the adom-owned sweep of packages that never declared a command anywhere (hero-studio, wiki-issue-loop class) continues as catalog hygiene per the previous comment.

Log in to reply.