Skip to content

Commit d1608ae

Browse files
committed
ROADMAP ultraworkers#121: hooks schema incompatible with Claude Code; error message misleading; doctor JSON emits 2 objects on failure breaking single-doc parsing; doctor has duplicate message+report fields
Dogfooded 2026-04-18 on main HEAD b81e642 from /tmp/cdWW. Four related findings in one: 1. hooks schema incompatible with Claude Code (primary): claw-code: {'hooks':{'PreToolUse':['cmd1','cmd2']}} Claude Code: {'hooks':{'PreToolUse':[ {'matcher':'Bash','hooks':[{'type':'command','command':'...'}]} ]}} Flat string array vs matcher-keyed object array. Incompatible. User copying .claude.json hooks to .claw.json hits parse-fail. 2. Error message misleading: 'field hooks.PreToolUse must be an array of strings, got an array' Both input and expected are arrays. Correct diagnosis: 'got an array of objects where array of strings expected' 3. Missing Claude Code hook event types: claw-code supports: PreToolUse, PostToolUse, PostToolUseFailure Claude Code supports: above + UserPromptSubmit, Notification, Stop, SubagentStop, PreCompact, SessionStart 5+ event types missing. matcher regex not supported. type: 'command' vs type: 'http' extensibility not supported. 4. doctor NDJSON output on failures: With failures present, --output-format json emits TWO concatenated JSON objects on stdout: Object 1: {kind:'doctor', has_failures:true, ...} Object 2: {type:'error', error:'doctor found failing checks'} python json.load() fails: 'Extra data: line 133 column 1' Flag name 'json' violated — NDJSON is not JSON. 5. doctor message + report byte-duplicated: .message and .report top-level fields have identical prose content. Parser ambiguity + byte waste. Trace: config.rs:750-771 parse_optional_hooks_config_object: optional_string_array(hooks, 'PreToolUse', context) Expects ['cmd1', 'cmd2']. Claude Code gives [{matcher,hooks:[{type,command}]}]. Schema-incompatible. config.rs:775-779 validate_optional_hooks_config: calls same parser. Error bubbles up. Message comes from optional_string_array path — technically correct but misleading. Fix shape (~200 lines + migration docs): - Dual-schema hooks parser: accept native + Claude Code forms - Add missing event types to RuntimeHookConfig - Implement matcher regex - Fix error message to distinguish array-element types - Fix doctor: single JSON object regardless of failure state - De-duplicate message + report (keep report, drop message) - Regression per schema form + event type + matcher Joins Claude Code migration parity (ultraworkers#103, ultraworkers#109, ultraworkers#116, ultraworkers#117, ultraworkers#119, ultraworkers#120) as 7th — most severe parity break since hooks is load-bearing automation infrastructure. Joins Truth-audit on misleading error message. Joins Silent-flag on --output-format json emitting NDJSON. Cross-cluster with Unplumbed-subsystem (ultraworkers#78, ultraworkers#96, ultraworkers#100, ultraworkers#102, ultraworkers#103, ultraworkers#107, ultraworkers#109, ultraworkers#111, ultraworkers#113) — hooks subsystem exists but schema incompatible with reference implementation. Natural bundles: Claude Code migration parity septet (grown flagship): ultraworkers#103 + ultraworkers#109 + ultraworkers#116 + ultraworkers#117 + ultraworkers#119 + ultraworkers#120 + ultraworkers#121 Complete coverage of every migration failure mode. ultraworkers#107 + ultraworkers#121 — hooks-subsystem pair: ultraworkers#107 hooks invisible to JSON diagnostics ultraworkers#121 hooks schema incompatible with migration source Filed in response to Clawhip pinpoint nudge 1494963222157983774 in #clawcode-building-in-public.
1 parent b81e642 commit d1608ae

1 file changed

Lines changed: 119 additions & 0 deletions

File tree

ROADMAP.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4084,3 +4084,122 @@ ear], /color [scheme], /effort [low|medium|high], /fast, /summary, /tag [label],
40844084
**Blocker.** Policy decisions (strict vs JSON5; alias table meanings; fallback mode when config drop happens) overlap with #86 + #87 + #115 + #116 decisions. Resolving all five together as a "permission-posture-plus-config-parsing audit" would be efficient.
40854085

40864086
**Source.** Jobdori dogfood 2026-04-18 against `/tmp/cdVV` on main HEAD `7859222` in response to Clawhip pinpoint nudge at `1494955670791913508`. Extends #86 (silent-drop) with novel JSON5-partial-acceptance angle + alias-collapse security inversion. Joins **Permission-audit / tool-allow-list** (#94, #97, #101, #106, #115) as 6th member — this is the CONFIG-PARSE anchor of the permission-posture problem, completing the matrix: #87 absence (no config), #101 env-var fail-OPEN, #115 init-generated dangerous default, **#120** config-drops-to-dangerous-default. Joins **Truth-audit / diagnostic-integrity** on the `loaded_config_files=0` + `permission_mode=danger-full-access` inconsistency. Joins **Reporting-surface / config-hygiene** (#90, #91, #92, #110, #115, #116) on the silent-drop-plus-no-stderr-plus-exit-0 axis. Joins **Claude Code migration parity** (#103, #109, #116, #117, #119) as 6th — claw-code is strict-where-Claude-was-lax (#116) AND lax-where-Claude-was-strict (#120). Natural bundle: **#86 + #120** — config-parse reliability pair: silent-drop general case (#86) + JSON5-partial-acceptance + alias-inversion security flip (#120). Also **permission-drift-at-every-boundary 4-way**: #87 + #101 + #115 + **#120** — absence + env-var + init-generated + config-drop. Complete coverage of how a workspace can end up at `DangerFullAccess`. Also **Jobdori+gaebal-gajae mega-bundle** ("security-critical permission drift audit"): #86 + #87 + #101 + #115 + #116 + **#120** (five-way sweep of every path to wrong permissions). Session tally: ROADMAP #120.
4087+
4088+
121. **`hooks` configuration schema is INCOMPATIBLE with Claude Code. claw-code expects `{"hooks": {"PreToolUse": [<command-string>, ...]}}` — a flat array of command strings. Claude Code's schema is `{"hooks": {"PreToolUse": [{"matcher": "<tool-name>", "hooks": [{"type": "command", "command": "..."}]}]}}` — a matcher-keyed array of objects with nested command arrays. A user migrating their Claude Code `.claude.json` hooks block gets parse-fail: `field "hooks.PreToolUse" must be an array of strings, got an array (line 3)`. The error message is ALSO wrong — both schemas use arrays; the correct diagnosis is "array-of-objects where array-of-strings was expected." Separately, `claw --output-format json doctor` when failures present emits TWO concatenated JSON objects on stdout (`{kind:"doctor",...}` then `{type:"error",error:"doctor found failing checks"}`), breaking single-document parsing for any claw that does `json.load(stdout)`. Doctor output also has both `message` and `report` top-level fields containing identical prose — byte-duplicated** — dogfooded 2026-04-18 on main HEAD `b81e642` from `/tmp/cdWW`.
4089+
4090+
**Concrete repro.**
4091+
```
4092+
# Claude Code hooks format:
4093+
$ cat > .claw/settings.json << 'EOF'
4094+
{
4095+
"hooks": {
4096+
"PreToolUse": [
4097+
{
4098+
"matcher": "Bash",
4099+
"hooks": [
4100+
{"type": "command", "command": "echo PreToolUse-test >&2"}
4101+
]
4102+
}
4103+
]
4104+
}
4105+
}
4106+
EOF
4107+
4108+
$ claw --output-format json status 2>&1 | head
4109+
{"error":"runtime config failed to load: /private/tmp/cdWW/.claw/settings.json: field \"hooks.PreToolUse\" must be an array of strings, got an array (line 3)","type":"error"}
4110+
# Error message: "must be an array of strings, got an array" — both are arrays.
4111+
# Correct diagnosis: "got an array of objects where an array of strings was expected."
4112+
4113+
# claw-code's own expected format (flat string array):
4114+
$ cat > .claw/settings.json << 'EOF'
4115+
{"hooks": {"PreToolUse": ["echo hook-invoked >&2"]}}
4116+
EOF
4117+
$ claw --output-format json status | jq .permission_mode
4118+
"danger-full-access"
4119+
# Accepted. But this is not Claude Code format.
4120+
4121+
# Claude Code canonical hooks:
4122+
# From Claude Code docs:
4123+
# {
4124+
# "hooks": {
4125+
# "PreToolUse": [
4126+
# {
4127+
# "matcher": "Bash|Write|Edit",
4128+
# "hooks": [{"type": "command", "command": "./log-tool.sh"}]
4129+
# }
4130+
# ]
4131+
# }
4132+
# }
4133+
# None of the Claude Code hook features (matcher regex, typed commands,
4134+
# PostToolUse/Notification/Stop event types) are supported.
4135+
4136+
# Separately: doctor NDJSON output on failures:
4137+
$ claw --output-format json doctor 2>&1 | python3 -c "
4138+
import json,sys; text=sys.stdin.read(); decoder=json.JSONDecoder()
4139+
idx=0; count=0
4140+
while idx<len(text):
4141+
while idx<len(text) and text[idx].isspace(): idx+=1
4142+
if idx>=len(text): break
4143+
obj,end=decoder.raw_decode(text,idx); count+=1
4144+
print(f'Object {count}: keys={list(obj.keys())[:5]}')
4145+
idx=end
4146+
"
4147+
Object 1: keys=['checks', 'has_failures', 'kind', 'message', 'report']
4148+
Object 2: keys=['error', 'type']
4149+
# Two concatenated JSON objects on stdout. python json.load() fails with
4150+
# "Extra data: line 133 column 1".
4151+
4152+
# Doctor message + report duplication:
4153+
$ claw --output-format json doctor 2>&1 | jq '.message == .report'
4154+
true
4155+
# Byte-identical prose in two top-level fields.
4156+
```
4157+
4158+
**Trace path.**
4159+
- `rust/crates/runtime/src/config.rs:750-771` — `parse_optional_hooks_config`:
4160+
```rust
4161+
fn parse_optional_hooks_config_object(...) -> Result<RuntimeHookConfig, ConfigError> {
4162+
let Some(hooks_value) = object.get("hooks") else { return Ok(...); };
4163+
let hooks = expect_object(hooks_value, context)?;
4164+
Ok(RuntimeHookConfig {
4165+
pre_tool_use: optional_string_array(hooks, "PreToolUse", context)?.unwrap_or_default(),
4166+
post_tool_use: optional_string_array(hooks, "PostToolUse", context)?.unwrap_or_default(),
4167+
post_tool_use_failure: optional_string_array(hooks, "PostToolUseFailure", context)?
4168+
.unwrap_or_default(),
4169+
})
4170+
}
4171+
```
4172+
`optional_string_array` expects `["cmd1", "cmd2"]`. Claude Code gives `[{"matcher": "...", "hooks": [{...}]}]`. Schema incompatible.
4173+
- `rust/crates/runtime/src/config.rs:775-779` — `validate_optional_hooks_config` calls the same parser; the error message "must be an array of strings" comes from `optional_string_array`'s path — but the user's actual input WAS an array (of objects). The message is technically correct but misleading.
4174+
- Claude Code hooks doc: `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `Notification`, `Stop`, `SubagentStop`, `PreCompact`, `SessionStart`. claw-code supports 3 event types. 5+ event types missing.
4175+
- `matcher` regex per hook (e.g. `"Bash|Write|Edit"`) — not supported.
4176+
- `type: "command"` vs `type: "http"` etc. (Claude Code extensibility) — not supported.
4177+
- `rust/crates/rusty-claude-cli/src/main.rs` doctor path — builds `DoctorReport` struct, renders BOTH a prose report AND emits it in `message` + `report` JSON fields. When failures present, appends a second `{"type":"error","error":"doctor found failing checks"}` to stdout.
4178+
4179+
**Why this is specifically a clawability gap.**
4180+
1. *Claude Code migration parity hard-block.* Users with existing `.claude.json` hooks cannot copy them over. Error message misleads them about what's wrong. No migration tool or adapter.
4181+
2. *Feature gap: no matchers, no event types beyond 3.* PreToolUse/PostToolUse/PostToolUseFailure only. Missing Notification, UserPromptSubmit, Stop, SubagentStop, PreCompact, SessionStart — all of which are documented Claude Code capabilities claws rely on.
4182+
3. *Error message lies about what's wrong.* "Must be an array of strings, got an array" — both are arrays. The correct message would be "expected an array of command strings, got an array of objects (Claude Code hooks format is not supported; see migration docs)."
4183+
4. *Doctor NDJSON output breaks JSON consumers.* `--output-format json` promises a single JSON document per the flag name. Getting NDJSON (or rather: concatenated JSON objects without line separators) breaks every `json.load(stdout)` style consumer.
4184+
5. *Byte-duplicated prose in `message` + `report`.* Two top-level fields with identical content. Parser ambiguity (which is the canonical source?). Byte waste.
4185+
6. *Joins Claude Code migration parity* (#103, #109, #116, #117, #119, #120) as 7th member — hooks is the most load-bearing Claude Code feature that doesn't work. Users who rely on hooks for workflow automation (log-tool-calls.sh, format-on-edit.sh, require-bash-approval.sh) cannot migrate.
4186+
7. *Joins truth-audit* — the diagnostic surface lies with a misleading error message.
4187+
8. *Joins silent-flag / documented-but-unenforced* — `--output-format json` says "json" not "ndjson"; violation of the flag's own semantics.
4188+
4189+
**Fix shape — extend the hooks schema to accept Claude Code format.**
4190+
1. *Dual-schema hooks parser.* Accept either form:
4191+
- claw-code native: `["cmd1", "cmd2"]`
4192+
- Claude Code: `[{"matcher": "pattern", "hooks": [{"type": "command", "command": "..."}]}]`
4193+
Translate both to the internal `RuntimeHookConfig` representation. ~80 lines.
4194+
2. *Add the missing event types.* Extend `RuntimeHookConfig` to include `UserPromptSubmit`, `Notification`, `Stop`, `SubagentStop`, `PreCompact`, `SessionStart`. ~50 lines.
4195+
3. *Implement matcher regex.* When a Claude Code-format hook includes `"matcher": "Bash|Write"`, apply the regex against the tool name before firing the hook. ~30 lines.
4196+
4. *Fix the error message.* Change "must be an array of strings" to "expected an array of command strings. Claude Code hooks format (matcher + typed commands) is not yet supported — see ROADMAP #121 for migration path." ~10 lines.
4197+
5. *Fix doctor NDJSON output.* Emit a single JSON object with `has_failures: true` + `error: "..."` fields rather than concatenating a separate error object. ~15 lines.
4198+
6. *De-duplicate `message` and `report`.* Pick one (`report` is more descriptive for a doctor JSON surface); drop `message`. ~5 lines.
4199+
7. *Regression tests.* (a) Claude Code hooks format parses and runs. (b) Native-format hooks still work. (c) Matcher regex matches correct tools. (d) All 8 event types dispatch. (e) Doctor failure emits single JSON object. (f) Doctor JSON has no duplicated fields.
4200+
4201+
**Acceptance.** A user's `.claude.json` hooks block works verbatim as `.claw.json` hooks. Error messages correctly distinguish "wrong type for array elements" from "wrong element structure." `claw --output-format json doctor` emits exactly ONE JSON document regardless of failure state. No duplicated fields.
4202+
4203+
**Blocker.** Implementation work is sizable (~200 lines + tests + migration docs). Product decision needed: full Claude Code hooks compatibility as a goal, or subset-plus-adapter. The current schema is claw-code-native; Claude Code compat requires either extending or replacing.
4204+
4205+
**Source.** Jobdori dogfood 2026-04-18 against `/tmp/cdWW` on main HEAD `b81e642` in response to Clawhip pinpoint nudge at `1494963222157983774`. Joins **Claude Code migration parity** (#103, #109, #116, #117, #119, #120) as 7th member — the most severe parity break since hooks is load-bearing automation infrastructure. Joins **Truth-audit / diagnostic-integrity** on misleading error message axis. Joins **Silent-flag / documented-but-unenforced** on NDJSON-output-violating-json-flag. Cross-cluster with **Unplumbed-subsystem** (#78, #96, #100, #102, #103, #107, #109, #111, #113) — hooks subsystem exists but schema is incompatible with the reference implementation. Natural bundle: **Claude Code migration parity septet (grown)**: #103 + #109 + #116 + #117 + #119 + #120 + **#121**. Complete coverage of every migration failure mode: silent drop (#103) + stderr prose warnings (#109) + hard-fail on unknown keys (#116) + prompt corruption from muscle memory (#117) + slash-verb fallthrough (#119) + JSON5-partial-accept + alias-inversion (#120) + hooks-schema-incompatible (#121). Also **#107 + #121** — hooks-subsystem pair: #107 hooks invisible to JSON diagnostics + #121 hooks schema incompatible with migration source. Also **NDJSON-violates-json-flag 2-way (new)**: #121 + probably more; worth sweep. Session tally: ROADMAP #121.

0 commit comments

Comments
 (0)