Found while implementing #15254 (PR #15493); filed as a finding only, not claimed.
Measured
indexObjectGraph (packages/lint/src/object-graph.ts:159) maps stack.objects through a local asArray that returns an ARRAY unchanged, so a null member reaches strName(obj.name) and dereferences null:
TypeError: Cannot read properties of null (reading (quote)name(quote))
at indexObjectGraph src/object-graph.ts:159:30
Reproduce with any member of the family, e.g. validateObjectFieldRefs({ objects: [null] }) or validateListViewFieldRefs({ objects: [null] }).
Why it matters more than a junk-input nicety
indexObjectGraph is the FIRST statement of every rule that resolves a field path — the shared seam #14105/#14148 introduced precisely so these rules cannot drift. Each of those rules guards its own per-object loop with if (!isRec(obj)) continue, but that guard runs AFTER the indexer has already thrown, so the guard never gets to help.
These rules are pure (stack) => Finding[] and run on the raw lint path as well as the parsed one, and at the runtime publish gate they are called inside the gate rather than behind a try/catch of their own. A throw there is not a skipped finding — it is an exception on a write path, which is a different and louder failure than the silent-miss class this family exists to end.
Suggested shape
One guard in the shared seam closes the whole class, rather than one guard per member: have asArray (or the loop) drop non-record entries in object-graph.ts. Sibling rules already spell the same defensive read in their own asArray copies, so the seam is the outlier.
Deliberately NOT fixed inside #15254: a local guard in one member would leave the identical crash in every sibling and hide it behind one green test.
Generated by Claude Code
Found while implementing #15254 (PR #15493); filed as a finding only, not claimed.
Measured
indexObjectGraph(packages/lint/src/object-graph.ts:159) mapsstack.objectsthrough a localasArraythat returns an ARRAY unchanged, so a null member reachesstrName(obj.name)and dereferences null:Reproduce with any member of the family, e.g.
validateObjectFieldRefs({ objects: [null] })orvalidateListViewFieldRefs({ objects: [null] }).Why it matters more than a junk-input nicety
indexObjectGraphis the FIRST statement of every rule that resolves a field path — the shared seam #14105/#14148 introduced precisely so these rules cannot drift. Each of those rules guards its own per-object loop withif (!isRec(obj)) continue, but that guard runs AFTER the indexer has already thrown, so the guard never gets to help.These rules are pure
(stack) => Finding[]and run on the rawlintpath as well as the parsed one, and at the runtime publish gate they are called inside the gate rather than behind a try/catch of their own. A throw there is not a skipped finding — it is an exception on a write path, which is a different and louder failure than the silent-miss class this family exists to end.Suggested shape
One guard in the shared seam closes the whole class, rather than one guard per member: have
asArray(or the loop) drop non-record entries inobject-graph.ts. Sibling rules already spell the same defensive read in their ownasArraycopies, so the seam is the outlier.Deliberately NOT fixed inside #15254: a local guard in one member would leave the identical crash in every sibling and hide it behind one green test.
Generated by Claude Code