Refusal memory keys on a 2-word verb class: ssh raw-ovh / docker exec become session-length trip wires after one refusal
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 68/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
Research direction
Start in index.ts at normalizeRefusalTarget around line 2770, priorRefusalFor around 3081, and normalizeGrantTarget around 2852. Trace how refusal keys are stored and how liftRefusals matches them, then verify that compound-aware grant keys distinguish read-only targets while model refusals still expire by evidence fingerprint.
Written by the indexing model from the issue text.
Description
Problem
Refusal memory keys on the first two words of the command (normalizeRefusalTarget, index.ts:2770: words.slice(0, words[0] === "git" ? 3 : 2)). On a session that talks to one deploy host, that collapses unrelated operations onto one key.
From the production decisions log, 2026-09-12 to 2026-09-17: 152 blocks carry prior refusal as a contributing reason. Top targets and what actually ran behind them:
| refusal target | blocks | what the blocked commands actually were |
|---|---|---|
ssh raw-ovh |
38 | wp db query "SELECT …", ls /tmp/sr201-*.php, mysql --version, grep -o RUSH_INTAKE_KEY …, unlink /tmp/sr201-prod-measure.php (all reads or scratch-file ops) |
docker exec |
64 total, 20 since 09-16 | docker exec raw_wordpress sh -c 'cp -a /var/www/html /tmp/… && ls', docker exec raw_wordpress php -r 'define("WP_USE_THEMES…' (read-only probes) |
docker compose |
28 | docker compose exec -T wordpress php -r '…' (read-only) |
cat > |
12 | heredoc scratch-file writes under /tmp |
Root cause
The 2-word key makes ssh <host> one permanent trigger for the session, whether the refused command was a destructive remote SQL delete or a SELECT. Human/critical/cap refusals do not expire mid-session (priorRefusalFor, index.ts:3081: only source !== "model" refusals clear on a changed evidence fingerprint; the rest are sticky until explicitly approved or the session ends), so one noisy early refusal of a remote cleanup turns every later read-only ssh inspection into a forced dialog for the rest of a long session.
The comment at index.ts:3092 already states the intent — "Model refusals are a warning about the facts the judge saw, not a permanent ban on a normalized verb" — but the same sentence's logic stops at model-source refusals, and the normalized key is too coarse for the fallback cases to be right either: a human refusing ssh raw-ovh '<destructive SQL>' did not thereby refuse ssh raw-ovh 'ls /tmp'.
Proposal
Move the human/critical/cap fallback from "same 2-word verb class" to "same key as the grant path already uses." normalizeGrantTarget (index.ts:2852) exists for exactly this reason and says so in its own comment: refusing to notice that difference turned a grant into an overgrant. Refusal over-match is the same bug in the safe direction — it costs dialogs, not data, but it costs them every session, and it is the single largest named refusal class in the log.
Concretely: index refusals by normalizeGrantTarget (verb + flags + first arg, compound-aware) instead of normalizeRefusalTarget, keep the evidence-fingerprint expiry for model refusals, and let liftRefusals keep working unchanged (it already clears by normalizedTarget + cwd, so tightening the key only narrows what it lifts).
Non-goal
Not asking to weaken refusal memory for genuinely destructive commands. The measured complaint is dialogs on repeated read-only inspection of the same host/container in a session that also refused one destructive operation once. A tighter key preserves the sticky-human rule; it just stops making ssh <host> and docker exec into session-length trip wires.
Related
normalizeRefusalTargetandnormalizeGrantTargetdeliberately disagree today (index.ts:2843comment calls the over-match "the SAFE direction for refusal memory"). This issue asks to close that gap now that the log shows its size.- Measured friction class, not hypothetical: 152 blocks since 09-12, ~9% of all blocks in the window.
- Interaction with omp-classifier#63: none directly, but the same log shows the git-flow family also false-asks; that issue and this one are the top two measured dialog sources.
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 1
- Avg merge
- 1h 20m
- Merged PRs (30d)
- 32
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from STRML/omp-classifier
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
STRML/omp-classifier#81 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
STRML/omp-classifier#80 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
STRML/omp-classifier#78 ·
-
Difficulty 5/5 Over a week Newbie friendliness 30/100
STRML/omp-classifier#84 ·
-
Difficulty 3/5 1-2 days Newbie friendliness 72/100
STRML/omp-classifier#82 ·
All issues in STRML/omp-classifier
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Eynzof/Hermes-CN-Desktop#610 ·
-
bug clawsweeper:linked-pr-open clawsweeper:needs-live-repro clawsweeper:no-new-fix-pr impact:message-loss issue-rating: 🐚 platinum hermit P2 regression
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
calcite-components needs triage refactor
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Esri/calcite-design-system#15203 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
danielmiessler/LifeOS#2218 ·