Fix workspace context review findings

This commit is contained in:
Yichen Jiang
2026-07-13 16:31:03 +08:00
parent adf6b8a1ab
commit aa62b5109a
40 changed files with 708 additions and 252 deletions
@@ -35,9 +35,9 @@ The CC bridge's `ask` result is a real permission path, not a terminal bridge de
`agent.inject()` defaults a missing `MessageSource` to `{ kind: 'user' }` — which would record plugin-injected context as if the user had typed it. So every bridge `inject()` and every `HookContext` passes an explicit `{ kind: 'plugin', plugin: 'hooks-claude' | 'hooks-codex' }` source. A test asserts the resulting `context/message.source` is the plugin, never `user`.
### Adding context is not a veto — delegate, then fold
### Adding context is not a veto — delegate, then prepend
A hook that only attaches `additionalContext` (no block/deny) is NOT a decision the bridge should return on its own: returning `allow`/`accept` from a waterfall listener WITHOUT calling `next()` short-circuits every later `agent/prompt-submit` / `tools/post-execute` listener, so a policy/sandbox plugin registered after the bridge would never see the prompt. Each bridge therefore delegates via `next()` before adding its context to the downstream decision. The two seams differ: `tools/post-execute` carries an ordered `additionalContexts` array, so the bridge prepends its separately sourced context while preserving a downstream `block` or `accept`; Code Mode ferries the same array through the outer `run_code` result. `agent/prompt-submit` still has one `additionalContext`, so an allowed downstream contribution is folded into one context while a downstream block drops it because a blocked prompt never reaches the model. Only a real `deny`/`block` from the hook itself short-circuits. Tests assert a later listener can still block a prompt a context-only hook allowed and that every post-tool context retains its own source, envelope, and metadata.
A hook that only attaches `additionalContext` (no block/deny) is NOT a decision the bridge should return on its own: returning `allow`/`accept` from a waterfall listener WITHOUT calling `next()` short-circuits every later `agent/prompt-submit` / `tools/post-execute` listener, so a policy/sandbox plugin registered after the bridge would never see the prompt. Each bridge therefore delegates via `next()` before adding its context to the downstream decision. Both seams carry ordered `additionalContexts` arrays, so the bridge prepends its separately sourced entry while preserving every downstream source, envelope, and metadata field; a downstream prompt block still drops all context because the prompt never reaches the model, while post-tool block semantics may explicitly retain contexts. Code Mode ferries the same array through the outer `run_code` result. Only a real `deny`/`block` from the hook itself short-circuits. Tests assert a later listener can still block a prompt a context-only hook allowed and that retained prompt and post-tool contexts remain separate.
### CLAUDE_PROJECT_DIR defaults to the session workspace