Skip to content

os validate parses the normalized stack WITHOUT lowering inline handlers, so the hook write-set family (hook-api-update-readonly-field and siblings) cannot see a handler-authored hook there — os validate passes a stack os build refuses #16544

Description

@baozhoutao

Filed unassigned from the #16095 door enumeration (branch claude/issue-16095-hook-rules-reach-handler-authored-hooks). Measured on origin/main @ 5d55afec4d through the real CLI (packages/cli/test/lint-hook-rules-reach-handler-hooks.e2e.test.ts on that branch pins the reading).

The reading

Every rule in the hook-body-* / hook-api-update-readonly-* family opens on body.language === 'js'. Whether a hook authored as handler: async (ctx) => { … } reaches it is a property of what each command hands the rule registry:

door lowers inline handlers before the rules run? handler-authored readonly write via ctx.api
os build yes — lowerCallables runs before ObjectStackDefinitionSchema.safeParse (compile.ts) refused, hook-api-update-readonly-field, exit 1
os lint since #16095lintConfig hands the parsed tier the lowered view refused, exit 1
os validate novalidate.ts parses normalized directly passes, exit 0, no finding

Control on the same fixture: the identical statement authored as an explicit body: { language: 'js', source } is refused by os validate (exit 1, same rule). So the silence is the door, not the rule.

This is the #4409 / #3782 class ("one command returns clean for a stack another refuses"), on the hook surface: the reference-integrity suite is gating and registered for all three commands, but os validate gives it an input on which the hook members cannot fire.

Why it was not folded into #16095

#16095's ask and ruling were scoped to os lint; making os validate lower would change what os validate refuses (a stack that validates green today would exit 1), which is its own accept/reject decision. Also note os validate runs lintUnknownStackKeys / lintUnknownAuthoringKeys pre-parse on the same normalized stack; a lowering pass would have to sit between those and the parse, mirroring compile.ts step 2b.

Suggested shape

Run lowerCallables(normalized) in validate.ts before the parse and parse lowering.lowered, exactly as compile.ts does — the same call, not a copy — and hand the registry parsed: result.data. The card's control fixture is the red-first test.

Blast radius on this tree: zero on the ten objectstack.config.ts corpus files (none of the repo's handler-authored hooks writes through ctx.api; the two that exist stamp ctx.input). Outside this tree the affected population is the one #16095 measured: 39 of 39 hooks in the reference CRM are handler-authored.

Related: #16095 (lint half, in flight) · #3782 (closed — the same class on flows) · #13838 (open — os build acceptance, deliberately untouched).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions