From deed51a397be97ba31fa0811bf9ca75686137e99 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 12:03:48 +0000 Subject: [PATCH] fix: recurse into NullableSchema.inner in assertValidSchema assertValidSchema validated array items, record values, and object property schemas but skipped the inner schema of NullableSchema. A misconfigured nested schema like s.nullable(s.object({ x: 'bad' as any })) would silently escape the early construction-time check and only surface a confusing error at invocation time or, worse, produce a repair loop. This adds a nullable branch that recurses into schema.inner before the properties branch, making schema validation consistent for all composite schema types. Discovered while running sample 13-test-plan.ts: the sample uses s.nullable indirectly through s.object with enum fields, and analysing assertValidSchema revealed the gap. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- skills/rig/rig.ts | 4 ++++ src/rig.test.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/skills/rig/rig.ts b/skills/rig/rig.ts index 34ab2e0..1bf3059 100644 --- a/skills/rig/rig.ts +++ b/skills/rig/rig.ts @@ -1687,6 +1687,10 @@ function assertValidSchema(schema: Schema, agentName: string, slot: "input" | "o assertValidSchema(schema.additionalProperties, agentName, slot, `${path}.*`); return; } + if ("nullable" in schema && schema.nullable === true) { + assertValidSchema((schema as NullableSchema).inner, agentName, slot, path); + return; + } if ("properties" in schema) { for (const [key, value] of Object.entries(schema.properties) as [string, Schema][]) { assertValidSchema(value, agentName, slot, `${path}.${key}`); diff --git a/src/rig.test.ts b/src/rig.test.ts index 741323d..86ba91f 100644 --- a/src/rig.test.ts +++ b/src/rig.test.ts @@ -141,6 +141,11 @@ describe("agent", () => { name: "implicit-nested", input: s.object({ text: "go" as any }), })).toThrow(/input\.text/); + + expect(() => agent({ + name: "implicit-nullable-nested", + output: s.nullable(s.object({ score: "bad" as any })), + })).toThrow(/output\.score/); }); it("does not expose deprecated hook APIs in core", () => {