Skip to content

ci(fork-sync): extend check-throwing-stub-callers.mjs with : never calibration signal (H7) #2435

@alexey-pelykh

Description

@alexey-pelykh

Status update (2026-04-20)

Originally scoped as a new separate scripts/check-throwing-stubs.mjs script. Re-scoped per 360 evaluation — the existing scripts/check-throwing-stub-callers.mjs (588 LOC, PR #2412, closing #2410) already implements substantially this gate with stronger discrimination than originally proposed here.

Re-scope decision: Add : never return type as a fourth calibration signal to the existing gate, rather than building a separate script.

Problem (unchanged)

When upstream sync re-introduces a previously-gutted function as an unconditional-throw stub, production code that imports it crashes user-visibly. CI passes because tests mock the stub. See #2408 for the canonical example.

Existing coverage

scripts/check-throwing-stub-callers.mjs classifies a function as a "throwing stub" when:

  1. Body is a single throw statement, AND
  2. At least one calibration signal fires:
    • Variadic-unknown signature: (...args: unknown[]) / (..._args: unknown[])
    • Fork-attributed throw message: /not available in RemoteClaw fork|\bgutted\b|upstream-compat/i
    • Leading comment matches /Gutted in RemoteClaw fork/i

Live non-test callers of a classified stub fail CI unless allowlisted.

#2408 would have been caught by this gate (variadic-unknown signature + fork-attributed message both present).

Gap being closed here

A throwing stub without any calibration signal — e.g., a plain function foo(): never { throw new Error("something") } reintroduced by upstream sync — would escape the existing gate. Adding : never return type as a fourth calibration signal closes that gap while preserving the existing typed-helper discrimination (typed error-throw helpers like exitHooksCliWithError(err: unknown): never, throwGatewayAuthResolutionError(reason: string): never are correctly excluded today and must remain excluded).

Solution

Modify scripts/check-throwing-stub-callers.mjs:

  1. Add : never return type detection as a fourth calibration signal (OR'd with the existing three)
  2. Preserve typed-param exclusion rule so legitimate typed error-throw helpers remain unflagged
  3. Add test cases covering the 4-signal interaction
  4. No separate script, no separate allowlist — violations go to .throwing-stub-callers-allowlist alongside the existing fix(agents): restore resolveAgentRuntimeOrThrow body — regression stub crashed every agent dispatch #2408 entry

Acceptance criteria

  • scripts/check-throwing-stub-callers.mjs extended: : never return type treated as calibration signal
  • Typed-param exclusion preserved — current 6-8 : never error-throw helpers (exitHooksCliWithError, throwGatewayAuthResolutionError, throwUnresolvedGatewaySecretInput, throwPathEscapesBoundary, local fail arrow functions in subagent-spawn/attachments, path-policy, etc.) remain unflagged
  • Unit tests added for the 4-signal interaction: (a) : never + throw + no other signal + typed param → NOT flagged (legitimate helper); (b) : never + throw + no other signal + variadic-unknown → FLAGGED
  • node scripts/check-throwing-stub-callers.mjs passes on current main (no new violations introduced by the extension)
  • node scripts/check-throwing-stub-callers.mjs --inventory output reviewed for shape changes
  • pnpm check passes after merge

Out of scope

  • New separate script (superseded — use existing gate)
  • Separate .throwing-stubs-allowlist.txt (superseded — .throwing-stub-callers-allowlist already exists)
  • Standalone pnpm lint:throwing-stubs script (superseded — existing CI job already wires the gate)
  • Full 21-entry allowlist seed (irrelevant — the typed-param exclusion means most current : never returns aren't flagged)

Effort

~0.5 day (targeted diff + tests on existing 588 LOC script)

Dependencies

Blocked by: — (re-scope removes dependency on authoring an ADR file; HQ ADR 0005 H7 description will be updated to describe the extension approach)
Tracked under: #2433

References

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions