fix: preserve object helpers for schema intersections#1975
Conversation
There was a problem hiding this comment.
LGTM. Right layer for the fix — .and() → .merge() at codegen restores .shape/.extend/.omit/.pick for every downstream consumer, not just the MCP tool registration path that surfaced the bug.
Things I checked
canSafelyMergesemantics (scripts/generate-zod-from-ts.ts:721). One-sided check is correct: allows disjoint keys, identical values, orleft = right.optional()/right.nullish()— the canonical envelope-optional/child-required AdCP wrap pattern, whereT.optional() ∩ T = Tand right-wins merge produce the same shape. The inverse (left required, right optional) is correctly rejected.- Fixpoint loop termination (
scripts/generate-zod-from-ts.ts:825). Each non-fixed-point iteration strictly decreases.and(count → terminates. - 48 substitutions in
schemas.generated.tsall land onz.object(...).passthrough()or named*Schemaresolvable to az.objectbody on both sides. The 7<union>.and(z.object(...))sites (e.g.GetRightsResponseSchema,VerifyBrandClaimsResponseBulkSchema) correctly stay.and()because the union LHS yieldsundefinedfromschemaShapeForExpression.SearchBrandsResponseSchemacorrectly merges the two envelope wraps and leaves the trailing.and(z.union(...))intact. isPlainZodObjectTail(scripts/generate-zod-from-ts.ts:622) only allows.passthrough()/.strict()/.strip()— verified thedoes not treat trailing-combinator schemas as ZodObject basestest intest/generate-zod-object-intersections.test.jscovers the.refine()and trailing.and(z.union(...))cases that would otherwise mis-merge.- Load-bearing assertion (
test/lib/zod-schemas.test.js:634-645). TheObject.keys(TOOL_REQUEST_SCHEMAS).filter(t => !TOOL_INPUT_SHAPES[t])parity check is the regression guard that would have caughtvalidate_property_deliverydisappearing the first time. Specifically assertsTOOL_INPUT_SHAPES.validate_property_delivery.list_idis non-undefined — exactly the field the MCP tool registration needed. - Changeset severity —
patchis correct. Bug fix restoring documented helper access, semantically equivalent rewrites, no public API addition.
Follow-ups (non-blocking — file as issues)
- Silent skip in
TOOL_INPUT_SHAPESconstruction.src/lib/schemas/index.ts:72-75doesflatMap → []whenshapeOfreturns undefined. The new test catches theTOOL_REQUEST_SCHEMAScodepath, but any other consumer of.shapeon a non-mergeable intersection still silently degrades. Replace the silent drop with an explicit throw atTOOL_INPUT_SHAPESconstruction so future regressions surface loudly. (javascript-protocol-expert's recommendation.) - Subsume earlier
extractObjectSchemahelper? TheextractObjectSchema/ProductObjectSchemaworkaround from the recent ProductSchema DX fix solved the same problem class one layer up. Worth checking whether this codegen-level fix lets you deprecate it. isPlainZodObjectTailkeep-in-sync. Currently allows only.passthrough()/.strict()/.strip(). Ifts-to-zodever starts emitting.catchall(z.unknown())for an index signature, safe merges silently stop happening. One-line comment nearscripts/generate-zod-from-ts.ts:622pointing at ts-to-zod's tail emission would buy a heads-up later.
Minor nits (non-blocking)
- Typed-export regex.
scripts/generate-zod-from-ts.ts:738uses(?::[^=]+)?for optional type annotations. Today every typed export is plain; if a future schema picks up a type annotation with=inside (default-typed generic), the export will silently fall out of the rewriter's view. Latent papercut, not a bug. - Test harness spawns
npx tsxper test withmkdtempSyncinside the repo root (test/generate-zod-object-intersections.test.js:11). Cold tsx startup × 3 noticeably extendsnpm test, and aSIGKILLmid-run leaves orphan.zod-object-intersections-*dirs in the working tree.os.tmpdir()would be safer, or import the rewriter as a real export and skip the spawn.
Safe to merge.
Catch-up regen brings BillingNotSupportedDetails, CanonicalFormat* and other inline-union arrays forward from main. No source changes — followups from review are filed as issues per the reviewer's "non-blocking" steer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1f98187 to
cd004ab
Compare
|
Issue #1979 consolidates five non-blocking hardening items from review of this PR that overlap its diff. Lightweight enough to fold before merge if convenient — otherwise they'll resurface on #1979 post-merge.
Posted by the triage bot from #1979. Generated by Claude Code |
CI's `sync-schemas:all` step updates `src/lib/version.ts` to track the latest published library version. Sync locally so the validate step doesn't see drift. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
LGTM. Right fix at the right layer — the rewrite happens in codegen, so ZodObject helpers are restored at the source instead of papered over at every call site that needs .shape.
Things I checked
- Failure mode is real.
src/lib/server/create-adcp-server.tsreadsschema?.shapeandcontinues onundefined, with only alogger.warn.ZodIntersectionhas no.shape, soTasksGetRequestSchemaandValidatePropertyDeliveryRequestSchemawere silently skipped. New runtime test intest/lib/zod-schemas.test.jswalksTOOL_REQUEST_SCHEMASand asserts every entry has a matchingTOOL_INPUT_SHAPESregistration — that's the right gate. canSafelyMergedoesn't weaken validation in the accepted cases. The asymmetric optional check (left-optional + right-required is accepted; left-required + right-optional is rejected by string-compare) is correct:.and()of optional+required is required,.merge()of optional+required is required. The reverse (which would silently weaken) falls back to.and().z.infer<>stays stable.ZodIntersection<ZodObject<A>, ZodObject<B>>→A & B.ZodObject<A & B>→A & B. Adopters with explicitz.infer<typeof TasksGetRequestSchema>types see no observable change; they gain.shapenavigability.- Conservative tail allowlist (
isPlainZodObjectTail) is sound. Only.passthrough()/.strict()/.strip()count as ZodObject-preserving..refine(),.and(z.union(...)),.partial(),.catchall()correctly fall through to.and(). The negative generator test covers refined and union bases — good. - Changeset present,
patchis the right shape. Restores documented behavior (MCP tool registration), no API removal, no required-param flip. Runtime validation semantics unchanged at every site the safety check accepted.
Follow-ups (non-blocking — file as issues)
unknownKeyspolicy isn't part of the safety gate.canSafelyMergechecks shape keys, not the tail policy. Today every rewrite is.passthrough().and(.passthrough())so B-wins-on-merge is a no-op, but if ts-to-zod ever emitsA.strict().and(B.passthrough()), the rewrite silently relaxes strictness. Either capture the tail inextractObjectLiteralBodyand require equality, or just refuse the rewrite unless both sides end in.passthrough()(the only form ts-to-zod emits today). One-line guard, big future-proofing payoff.- Convergence loop re-scans the whole file each pass.
postProcessObjectIntersections:824-829callsextractSchemaExportson every iteration ofrewriteNamedObjectAnds. ~48 swaps × full file scan is fine at codegen scale today, sub-second, but the structure is quadratic. Worth memoizing the export map once and patching it after each rewrite if the generated file keeps growing. - Test gaps worth backfilling. The optional/nullish-narrowing acceptance path (left
z.string().optional()+ rightz.string()), the reverse-optional rejection, and a transitiveC = A.and(B); D = C.and(...)case that proves the convergence loop actually converges. Three small cases pin down the most load-bearing branches ofcanSafelyMerge.
Minor nits (non-blocking)
readPropertyKeyallows hyphens in bare identifiers atscripts/generate-zod-from-ts.ts:658. JS bare identifiers can't contain-. Today ts-to-zod doesn't emit invalid bare keys so it's never reached, but the regex invites a future footgun. Drop the-from the character class.- The optional-asymmetry in
canSafelyMergedeserves a comment atscripts/generate-zod-from-ts.ts:721-734. The check looks asymmetric on the page; a two-liner explaining "merge means B wins, so right-optional would silently drop left's required constraint" would stop a future maintainer from "symmetrizing" it and re-introducing the bug. - Hand-rolled parser doesn't skip
//or/* */. ts-to-zod doesn't emit comments inside schema expressions today, so this is theoretical, but a generator change could break it silently. A one-line assertion that the input contains no comment markers insideexport const ...Schema = ... ;ranges would catch drift loudly.
Approving on the strength of the codegen-layer fix plus the type-test + runtime registration test, which together prevent this exact regression from reappearing.
There was a problem hiding this comment.
LGTM. Real bug, sound fix, fail-closed gating. Restores .shape/.extend/.omit/.pick on ~48 generated schemas so validate_property_delivery and the other envelope/asset tools stop silently dropping out of TOOL_INPUT_SHAPES.
Things I checked
canSafelyMerge(scripts/generate-zod-from-ts.ts:721-733) — the three accepted cases (identical,${right}.optional(),${right}.nullish()) all preserve validation..and()requires both sides;.merge()keeps right. Intersection ∩ optional collapses to required, so the optional/nullish width-only allowances tighten correctly. Confirmed againstTasksGetResponseSchema's triple-merge withProtocolEnvelopeSchema.task_id.optional()vs the right's requiredtask_id— both forms resolve to required.isPlainZodObjectTail(scripts/generate-zod-from-ts.ts:622-633) — rejects bases ending in.refine()or.and(z.union(...)), only allows.passthrough()/.strict()/.strip(). Fail-closed. Negative test attest/generate-zod-object-intersections.test.js:79-104locks it.- Integration order at
scripts/generate-zod-from-ts.ts:1046— runs afterpostProcessForPassthrough(so.passthrough()tails are present for the predicate) and before the secondpostProcessUndefinedUnions(no structural conflict). Also beforepostProcessTS7056Annotations, which matters because the export regex(?::[^=]+)?would otherwise have to deal with type annotations. - Fixpoint loop in
postProcessObjectIntersections—rewriteNamedObjectAndsis monotonically non-increasing in\w+Schema.and(count; cannot diverge. - Unknown-key semantics — both envelope schemas (
AdCPVersionEnvelopeSchema,ProtocolEnvelopeSchema) arez.object({...}).passthrough(), and the rewritten literal re-wraps.passthrough()on the merge argument. Passthrough is preserved. - JSON Schema output via
toJSONSchema()—.merge()produces flat{"type":"object","properties":...}instead ofallOf, which is exactly the shape MCP clients prefer. Existing blanket regression test attest/lib/zod-schemas.test.js:557-582would have caught conversion failures across the ~48 flipped schemas. - Type test (
src/type-tests/zod-object-intersections.type-test.ts) — exercises.shape/.extend/.omit/.pickonValidatePropertyDeliveryRequestSchema,TasksGetRequestSchema,TasksGetResponseSchema,IndividualImageAssetSchema,GroupVideoAssetSchema,CreativeVariantSchema. - Runtime test (
test/lib/zod-schemas.test.js:606-646) — fullTOOL_REQUEST_SCHEMAS→TOOL_INPUT_SHAPEScoverage check with explicitvalidate_property_delivery.list_idassertion. Locks the bug. - Changeset is a
patch. Right call — runtime validation for valid inputs is unchanged; the helpers are documented behavior being restored, not new API surface. src/lib/version.ts8.1.0-beta.6 → 8.1.0-beta.8 is a routine LIBRARY_VERSION sync (matches the priorchore: sync LIBRARY_VERSIONmerge);package.jsonis not touched.
Follow-ups (non-blocking — file as issues)
scripts/generate-zod-from-ts.ts:769-798—schemaShapeForExpressiondoesn't recognize.merge(...)chains. After the first pass convertsA = B.and(z.object({...}))toA = B.merge(z.object({...})), any laterC = A.and(z.object({...}))cannot resolveA's shape and stays as.and(). Conservative (no correctness loss), but if a future tool drops out ofTOOL_INPUT_SHAPESdue to a chained envelope schema, this is the seam. Cheap fix: also recognize<expr>.merge(z.object({...}))and union the parsed shapes.test/generate-zod-object-intersections.test.js— no explicit unit case for the width-only-overlap rule (left =${right}.optional()). Exercised indirectly via theTasksGetruntime test; a direct unit test would lockcanSafelyMergeagainst upstream schema drift.scripts/generate-zod-from-ts.ts:1046— worth a one-line comment noting the position is load-bearing ("must run afterpostProcessForPassthroughand beforepostProcessTS7056Annotations") — symmetric to the existing ordering comments above.
Minor nits (non-blocking)
- Scanner domain.
scripts/generate-zod-from-ts.ts:526-566— the regex-literal heuristic is correct because the input is always post-ts-to-zod Zod source (no division operators in expression position), but a one-line comment naming that domain would deter future editors from repurposing the scanner against arbitrary TypeScript.
The unrelated npm test timeout on cli-webhook-receiver-flag.test.js and conformance-cli.test.js is noise — both pass when rerun individually and the change here doesn't touch either path.
Approving on the strength of the fail-closed canSafelyMerge gate plus the dual type-level + runtime regression coverage.
…mment, tmpdir fix - TOOL_INPUT_SHAPES: replace silent flatMap-skip with explicit throw so non-ZodObject entries surface at module init rather than disappearing from the map (item 1 from #1979 review) - isPlainZodObjectTail: add watchdog comment pointing at .catchall(z.unknown()) as a future watch item if ts-to-zod ever emits it (item 3) - generate-zod-object-intersections test: use os.tmpdir() for mkdtempSync prefix to avoid orphan dirs in repo root on SIGKILL (item 5) Items 2 (extractObjectSchema deprecation question) and 4 (regex latent gap) remain tracked in #1979 for post-merge follow-up.
|
Folded items 1, 3, and 5 from #1979 into this branch (commit 976e67d):
Deferred to #1979 post-merge:
All three object-intersection tests pass locally with the changes in place. Generated by Claude Code |
|
Acknowledged — commit 976e67d is on the branch head with items 1, 3, and 5 folded in. Items 2 and 4 tracked in #1979 for post-merge follow-up. Triaged by Claude Code. Generated by Claude Code |
There was a problem hiding this comment.
Generator-only fix to expose .shape/.extend/.omit/.pick on schemas that ts-to-zod was emitting as ZodIntersection. Right diagnosis: the silent flatMap-skip in TOOL_INPUT_SHAPES was hiding validate_property_delivery (and the 13 other envelope-inheriting tools). Right shape: the conservative canSafelyMerge keeps conflicting overlaps and trailing-union/refine bases as .and(), and the new module-init throw means the next regression gets caught at startup, not in production.
Hold: one-character typo at scripts/generate-zod-from-ts.ts:33
return content.replace(/(?<!\.\.never\(\))\.optional\(\)/g, '.nullish()');The extra \. makes the lookbehind require ..never() (two dots) before .optional(), which is unreachable. The original (?<!\.never\(\)) did real work — the comment immediately above says z.never().optional() must stay as-is because converting it to .nullish() weakens "must not be provided" to "null is fine." Currently latent because nothing in schemas.generated.ts matches z.never() today, but the next schema that introduces it will silently get rewritten. One-character fix; please fold into this PR rather than file a follow-up.
Things I checked
postProcessObjectIntersectionsparser:skipQuotedOrRegexLiteralhandles strings and regex literals, balanced scan covers()/{}/[]. Template literals with${}are not explicitly handled, but ts-to-zod doesn't emit them inside schema bodies.isPlainZodObjectTailwhitelist (.passthrough/.strict/.strip) leaves.refine/.transform/trailing.and(union)asZodIntersection— test case 3 intest/generate-zod-object-intersections.test.jspins this.canSafelyMergeasymmetry is sound: left=X.optional()+right=Xis allowed (right wins, tightens toX); reverse is rejected (would loosen). Conservative-failing is the right posture — a missed-safe-merge becomes a module-init throw caught by CI, not a silent regression.rewriteNamedObjectAndsfixed-point loop terminates monotonically; cycle guard viavisitinginschemaShapeForExpressioncorrectly threaded.SearchBrandsResponseSchemaatschemas.generated.ts:9220— first.and(ProtocolEnvelopeSchema)merged, second.and(z.union([Success, Error]))correctly kept as.and().- Fail-closed at
src/lib/schemas/index.ts:74-77. Module-init throw beats the previous silentflatMap-skip. Error message names both the key and the remedy ("use merge() not and()"). - All 48
.and(→.merge(rewrites inschemas.generated.ts. None of the affected envelope-inheriting schemas (Tasks*,ValidatePropertyDelivery*,ListCreativeFormatsRequestCreativeAgent,SearchBrandsResponse,ListAccountsResponse, 11Group*Asset/Individual*Asset) have spec-definedallOfsemantics — these are pure envelope/base inheritance and flat-merge is semantically equivalent. - 236-line
inline-enums.generated.tsrefresh matchesADCP_VERSION = '3.1.0-beta.3'— expected beta.3 cache drift (CanonicalFormat*family,query_upstream_trafficscenario,CreativePurgedWebhook.initiator, the asset_source/audio_channels deprecated-alias regeneration), not unexpected churn. - Wire equivalence: framework tools register with
PASSTHROUGH_INPUT_SCHEMA, so this only affectscustomToolForadopters who consumeTOOL_INPUT_SHAPESdirectly. For them, previously-skipped tools become available with proper shape rather than missing. Recovery, not regression. - Changeset present,
patch-level appropriate, prose names the user-visible effect.
Follow-ups (non-blocking — file as issues)
- Cross-import regex tightening at
scripts/generate-zod-from-ts.ts:969(;?\n+→; ?\n+) is unrelated to the stated fix. The defensive throw at lines 975-981 catches any silent no-op, so the practical risk is bounded — but the change still loosens robustness against an injector that ever omits the semicolon. Restore;?or document the requirement. - Header literal at line ~1078 was flattened from multi-line template to single-line
\nescapes. Drive-by cosmetic with no behavioral effect; pick one form and stop churning it. isPlainZodObjectTailwatch comment correctly flags.catchall(z.unknown())as a future ts-to-zod output. Thevalidate_property_delivery should be registeredassertion attest/lib/zod-schemas.test.js:637is a good spot-check; a cardinality canary (Object.keys(TOOL_REQUEST_SCHEMAS).length === Object.keys(TOOL_INPUT_SHAPES).length) would catch the same class of regression without needing to enumerate keys.
Ship after the line-33 typo is folded in.
There was a problem hiding this comment.
LGTM. Right call to fold the silent-skip into a throw — fail-closed beats fail-open, and the missing validate_property_delivery shape proves the prior flatMap was hiding a real bug.
Things I checked
canSafelyMergeatscripts/generate-zod-from-ts.ts:722-735is symmetric in the right direction: narrowing (left optional, right required) merges; widening (left required, right optional) stays as.and(). Both cases preserve AND-semantics under.passthrough()..and(z.union(...))patterns inschemas.generated.tsare correctly left alone —schemaShapeForExpressionreturnsundefinedfor unions, sorewriteTopLevelObjectAndsfalls through to.and(). VerifiedSearchBrandsResponseSchema = AdCPVersionEnvelopeSchema.merge(ProtocolEnvelopeSchema).and(z.union(...))atschemas.generated.ts:9220— the trailing union is preserved.isPlainZodObjectTailwhitelists exactly.passthrough() / .strict() / .strip()— anything richer (refine, transform, and-with-union) blocks the merge. The watchdog comment about.catchall(z.unknown())atscripts/generate-zod-from-ts.ts:622is the right discipline.schemaShapeForExpressionrecursion is bounded by thevisitingset atscripts/generate-zod-from-ts.ts:786, so circular named refs returnundefinedinstead of looping.- Test harness at
test/generate-zod-object-intersections.test.js:11-39usesmkdtempSyncinos.tmpdir()withforce: true, recursive: truecleanup — isolated and idempotent. - All 51 entries in
TOOL_REQUEST_SCHEMASresolve to ZodObjects with.shapetoday; the throw atsrc/lib/schemas/index.ts:73-77is enforced by the runtime test attest/lib/zod-schemas.test.js:633-644. The invariant has a test, not just hope. - Changeset is
patch— correct. The schemas are equivalent or narrower under merge, no public API removal, and the throw exposes a previously-silent bug rather than introducing a regression.
Follow-ups (non-blocking — file as issues)
- The
while (true)fixpoint loop atscripts/generate-zod-from-ts.ts:826-830is effectively a single pass — once a schema is rewritten toFoo.merge(z.object(...))its expression no longer matches the bare-named-ref or inline-z.objectpatterns thatschemaShapeForExpressionresolves, so subsequent passes never gain new shape info. Termination is guaranteed (strict reduction of.and(count) but the loop is misleading. Either drop the loop or add a one-liner explaining the intended iteration semantics. - Extend
isPlainZodObjectTailto accept.catchall(z.unknown())now rather than waiting for ts-to-zod to emit it. The watchdog comment is good; the proactive whitelist is better. - Changeset body could add "may surface previously-silent misconfigurations" so adopters with custom schema augmentations see the throw coming.
Minor nits (non-blocking)
- Export-regex assumption.
/export const (\w+Schema)(?::[^=]+)? = /gatscripts/generate-zod-from-ts.ts:739skips type annotations using[^=]+. Works today because no TS7056 annotation contains=. A future defaulted generic (<T = unknown>) would mis-match. Worth a short comment naming the assumption. - Module-load throw audibility.
src/lib/schemas/index.ts:73-77kills the wholeimport * as schemas from '@adcp/sdk/schemas'if any entry lacks.shape. That's correct fail-closed behavior, but a one-line comment near the throw stating "invariant enforced by test/lib/zod-schemas.test.js" would save the next reader a grep.
Approving on the strength of the merge-vs-AND analysis plus the fail-closed throw. The fixpoint-loop observation is interesting but not load-bearing.
* fix: preserve ProductSchema ZodObject ergonomics * chore: sync package lock versions * fix: unwrap named record union schema markers * chore: add ProductSchema DX changeset * fix: identifier-boundary guard + loud-fail in named-union unwrap Address two reviewer follow-ups on #1972 (`code-reviewer` and `javascript-protocol-expert` converged on the same nit independently): 1. Left identifier-boundary check in `unwrapNamedRecordUnionIntersections`. `content.startsWith(${name}.and(, i)` previously matched suffixes — `SizeModeMutexSchema` collected today, but a future `FooSizeModeMutexSchema` would silently corrupt output by emitting the prefix character and then the inner schema body. Gate on `i === 0 || !/[A-Za-z0-9_$]/.test(content[i-1])`. 2. Loud-fail when `readBalancedBody` returns undefined. Previously swallowed silently (advanced by name length, dropping content); now throws so a malformed ts-to-zod payload crashes codegen. Also extends `zod-schema-ergonomics.type-test.ts` with `.extend()` / `.omit()` / `.pick()` assertions against `CanonicalFormatDisplayTagSchema`, `CanonicalFormatImageSchema`, and `CanonicalFormatHTML5BannerSchema` — the three Pass 4 targets the original PR only covered via runtime `.d.ts` grep. Regen brings inline-enums and schemas.generated forward. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(codegen): fold #1979 hardening items before merge - Pair negative .d.ts intersection grep with runtime _def.typeName===ZodObject assertion in zod-schemas.test.js so the test fails closed if Zod adjusts its tuple stringification. - Note the z.object( body-prefix constraint in both unwrap docstrings so a future editor doesn't accidentally broaden the right-hand side. - Add TODO comment on the one-level limit in collectRedundantRecordUnionSchemaNames. Refs #1979 https://claude.ai/code/session_01768uE6DmJzUwwq4tpb4hqZ * fix(test): use ZodObject constructor name, not Zod 3 _def.typeName Triage commit fd68ac6 added a `ProductSchema._def.typeName === 'ZodObject'` assertion based on the reviewer's follow-up suggestion. The reviewer's suggested probe was written against Zod 3 — Zod 4 dropped `_def.typeName` in favor of `_def.type` ('object' lowercase) and the codebase imports Zod 4. The assertion was failing because `_def.typeName` is `undefined`. Use `constructor.name === 'ZodObject'` instead — stable across both Zod major versions and the same closed-fail behavior the reviewer asked for. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: regen schemas after rebase onto main with #1975 * chore: regen schemas after rebase onto main with #1981 --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
validate_property_deliverydisappearing fromTOOL_INPUT_SHAPESby regenerating object/object schema intersections as ZodObject.merge()where safe..shape,.extend(),.omit(), and.pick()for generated schemas built from safe object intersections..and()so validation is not silently weakened.Validation
npm run build:libnpm run typecheckNODE_ENV=test node --test-timeout=60000 --test-force-exit --test test/generate-zod-object-intersections.test.js test/lib/zod-schemas.test.jsNODE_ENV=test node --test-timeout=60000 --test-force-exit --test test/lib/cli-webhook-receiver-flag.test.jsNODE_ENV=test node --test-timeout=60000 --test-force-exit --test test/lib/conformance-cli.test.jsnpm testwas attempted and ran 10,322 tests; it exited nonzero becausetest/lib/cli-webhook-receiver-flag.test.jsandtest/lib/conformance-cli.test.jshit the 60s per-file timeout under the combined run. Both passed when rerun individually.Review