You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: terminal overlay for permission prompts + ask rule support (#15)
* feat(schema): add v2 with ask[] array support
* feat(add): support ask rules via --ask flag and write-rule.sh ask list
* feat(list/remove/suggest): ask rule support across CLI commands
* feat(hook): ask decision path with document-order match precedence
* feat(overlay): terminal multiplexer dialog skeleton for permission prompts
* feat(hook): overlay invocation with mode-based auto-allow short-circuit
* feat(overlay): /passthru:overlay command to toggle overlay on/off
* docs: document overlay + ask rules, bump v0.5.0
* fix(test): use sanitized PATH instead of broken symlinks for CI multiplexer masking
Paths honor `PASSTHRU_USER_HOME` and `PASSTHRU_PROJECT_DIR` so tests never touch the real `~/.claude`.
72
98
99
+
## Environment variables
100
+
101
+
Variables the plugin reads at runtime. Most are test-only overrides; a couple (`PASSTHRU_OVERLAY_TIMEOUT`, `PASSTHRU_WRITE_LOCK_TIMEOUT`) have user-facing meaning.
102
+
103
+
*`PASSTHRU_USER_HOME` - override `~/.claude` as the user scope root. Used by every bats test to redirect reads and writes to a temp dir. Never set in production.
104
+
*`PASSTHRU_PROJECT_DIR` - override `$PWD/.claude` as the project scope root. Same use case as above. Tests set both.
105
+
*`PASSTHRU_OVERLAY_RESULT_FILE` - path the overlay dispatcher writes the verdict line(s) into. Set by `pre-tool-use.sh` per-invocation via `sanitize_tool_use_id` + `passthru_tmpdir`. The overlay script reads the path from this env var; the hook reads back the contents after the overlay exits.
106
+
*`PASSTHRU_OVERLAY_TEST_ANSWER` - short-circuit the interactive keypress loop in `overlay-dialog.sh`. Accepts `yes_once|yes_always|no_once|no_always|cancel`. Used exclusively by `tests/overlay.bats` + `tests/hook_handler.bats` to exercise every branch without pseudo-tty gymnastics. Never set by the hook in production.
107
+
*`PASSTHRU_OVERLAY_TOOL_NAME` - tool name passed into the overlay dialog. Hook propagates the inbound `tool_name` field verbatim.
108
+
*`PASSTHRU_OVERLAY_TOOL_INPUT_JSON` - tool input JSON (stringified) passed into the overlay dialog. Hook propagates the inbound `tool_input` field verbatim. The dialog and `overlay-propose-rule.sh` parse it for the suggested-rule screen.
109
+
*`PASSTHRU_OVERLAY_TIMEOUT` - seconds to wait for a user response inside the overlay. Default 60. If the user does not respond in time, the overlay exits without writing a verdict and the hook treats the prompt as cancelled (falls through to the native dialog). Setting below 60 is fine; setting above requires also raising the PreToolUse hook timeout (currently 75s).
110
+
*`PASSTHRU_WRITE_LOCK_TIMEOUT` - seconds `scripts/write-rule.sh` and `scripts/remove-rule.sh` wait for the user-scope mkdir lock. Default 5. See the "Write-wrapper locking" section below.
111
+
73
112
## How tests run
74
113
75
114
All shell logic is covered by bats-core. Run the full suite:
@@ -141,11 +180,11 @@ Exit codes:
141
180
Checks performed (in order, across the merged set):
142
181
143
182
1.**parse** - every existing file is valid JSON.
144
-
2.**schema** - every rule has at least one of `tool` or `match`, types match spec, version is `1`.
183
+
2.**schema** - every rule has at least one of `tool` or `match`, types match spec, version is `1` or `2`. v2 files may declare `ask[]`; rules in `ask[]` are validated with the same rule-shape checks as `allow[]` and `deny[]`.
145
184
3.**regex** - every `tool` regex and every `match.*` regex compiles in perl.
146
185
4.**duplicates** - exact-duplicate rules (same tool + match) across scopes emit a warning.
147
-
5.**conflict** - identical `tool + match` appears in both `allow[]` and `deny[]` (merged) emits an error.
148
-
6.**shadowing** - within one merged `allow[]`or `deny[]` array, a later rule duplicates an earlier one. Warning.
186
+
5.**conflict** - identical `tool + match` appears in two or more of `allow[]`, `deny[]`, `ask[]` (merged) emits an error.
187
+
6.**shadowing** - within one merged `allow[]`, `deny[]`, or `ask[]` array, a later rule duplicates an earlier one. Warning.
149
188
150
189
## Write-wrapper locking
151
190
@@ -168,8 +207,26 @@ concurrent project shells.
168
207
169
208
## Hook timeout
170
209
171
-
Both `PreToolUse` and `PostToolUse` are registered with `"timeout": 10` in
172
-
`hooks/hooks.json`. The reason for 10 seconds (rather than 2-3):
210
+
`PostToolUse`, `PostToolUseFailure`, and `SessionStart` are registered with
211
+
short timeouts (10s / 10s / 5s) in `hooks/hooks.json`. `PreToolUse` runs with
212
+
a **75s** timeout because Task 8 (v0.5.0) wired the hook to block
213
+
synchronously on the interactive terminal-overlay dialog.
214
+
215
+
The 75s figure breaks down as:
216
+
217
+
* The overlay dialog (`scripts/overlay-dialog.sh`) enforces its own 60s
218
+
budget (`PASSTHRU_OVERLAY_TIMEOUT`, default 60s).
219
+
* Add 15s of margin for overlay launch, multiplexer roundtrip, post-dialog
220
+
rule write via `write-rule.sh`, and audit line emission.
221
+
* CC's hook timeout is wall-clock (confirmed via `time sleep 1`: 1.008s
222
+
real). Anything below the overlay's own budget would kill the hook
223
+
mid-dialog and lose the user's verdict.
224
+
225
+
The 10s baseline for non-overlay PreToolUse paths (rule match, mode
226
+
auto-allow) still applies in the sense that none of them block on IO; the
227
+
75s cap only matters when the overlay is actually invoked.
228
+
229
+
For post-event handlers, the original 10s baseline continues to hold:
173
230
174
231
*`load_rules` shells out to `jq` once per rule file (up to 4 files), once for
175
232
the parse check, once for normalization, and once for the merge.
@@ -183,8 +240,9 @@ Both `PreToolUse` and `PostToolUse` are registered with `"timeout": 10` in
183
240
audit fidelity, never block a tool call. Choosing 10s leaves 5x headroom
184
241
over typical worst case.
185
242
186
-
Lower the timeout only after profiling on the target hardware. Higher is
187
-
fine.
243
+
Lower the PreToolUse timeout only after also lowering
244
+
`PASSTHRU_OVERLAY_TIMEOUT` (and only after profiling on target hardware).
245
+
Raising it is always safe since the handler fails open on timeout.
188
246
189
247
## Releases
190
248
@@ -230,3 +288,6 @@ The release flow in one-line form:
230
288
* Adding a new hook event: register it in `hooks/hooks.json` and add a handler under `hooks/handlers/`. Reuse `hooks/common.sh` helpers where possible.
231
289
* Adding a new verifier check: see `CONTRIBUTING.md` section "Adding a new verifier check".
232
290
* Adding a new rule type or schema field: see `CONTRIBUTING.md` section "Rule schema evolution".
291
+
* Changing the overlay UI or keyboard flow: `scripts/overlay-dialog.sh` is the TUI, `scripts/overlay.sh` is the multiplexer dispatcher, and `scripts/overlay-propose-rule.sh` proposes the regex on A/D. Test via `PASSTHRU_OVERLAY_TEST_ANSWER`; see `tests/overlay.bats` for the stub-tmux pattern.
292
+
* Changing ask-rule semantics: the merged document-order logic sits in `hooks/common.sh` (`find_first_match`) and `hooks/handlers/pre-tool-use.sh`. Ask rule parsing + validation is in `validate_rules` + `load_rules`. The verifier's conflict and shadowing checks in `scripts/verify.sh` must also cover `ask[]`.
293
+
* Adding a new overlay multiplexer backend: add detection + launch lines in `scripts/overlay.sh` (search for the tmux / kitty / wezterm branches) and a stub fixture in `tests/fixtures/overlay/`. The shared detector helper lives in `hooks/common.sh` (`detect_overlay_multiplexer`).
0 commit comments