Skip to content

disk-hygiene: always-on engine gate and session belt emit identical denial text with opposite scopes #3348

Description

@kyle-sexton

plugins/disk-hygiene v0.20.21. Split out of the audit umbrella. F13, medium-high. Cheapest high-value fix in that report: a message change, no behavior change.

Problem

The plugin has two Bash-denying surfaces with opposite scopes:

  1. The always-on engine gate — registered in hooks/hooks.json on PreToolUse matcher Bash|PowerShell with no invocation predicate. Active in every session whether or not /disk-hygiene:clean was ever invoked.
  2. The session belt — the skill's frontmatter hooks, registered when the skill is invoked and staying registered for the rest of the session (disk-hygiene:clean: frontmatter Bash belt is session-lifetime, not skill-scoped (+6 findings from a post-use audit) #2618).

They emit identical denial text. A user who has never invoked the skill triggers the always-on gate and is told their Bash lane is restricted to the skill's scan/preview/handoff-verify/apply shapes — describing a lockout they did not opt into, in the words of a different guard.

Evidence

This misled the audit itself. A Bash denial was initially attributed to the session belt and written up as such. A two-cell control isolated it to the always-on gate, and the finding was withdrawn (F9.1/F9.2). If an audit reading the plugin's own source got this wrong from the message text, an ordinary user has no chance.

The withdrawal also corrected a related claim: the belt does not reach subagents. That non-inheritance is undocumented and build-specific, so it should not be documented as a recovery lane without that caveat.

Suggested fix

Branch the denial message on resolve_mode() so each guard explains itself:

  • Always-on gate: say that this specific engine invocation is what is gated, that the rest of the Bash lane is unaffected, and that the skill need not have been invoked for this to fire.
  • Session belt: say that /disk-hygiene:clean was invoked in this session, that the belt persists until the session ends, and name the recovery lanes — including that a subagent does not inherit it, caveated as undocumented.

No behavior change, no scope change. The two guards keep doing exactly what they do; they stop claiming to be each other.

Related

Umbrella: #3347. F9.3/F9.4 in that issue cover the belt's missing escape hatch and its over-broad scope, which are separate and larger.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triagepriority: mediumReal value, no hard deadline; normal backlog flow.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions