Skip to content

feat: return input schema on start() invalid-input errors - #1275

Open
MQ37 wants to merge 7 commits into
masterfrom
feat/call-actor-input-error-schema
Open

feat: return input schema on start() invalid-input errors#1275
MQ37 wants to merge 7 commits into
masterfrom
feat/call-actor-input-error-schema

Conversation

@MQ37

@MQ37 MQ37 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

What

When actor.start() is rejected by the platform with a confirmed invalid-input 400 (real-schema validation, not our own AJV copy), call-actor/call-actor-widget and direct per-Actor tools now return the Actor's input schema and the platform's own message instead of falling through to the generic "verify the Actor name, input parameters, and ensure the Actor exists" text. No extra network call — the schema was already fetched during resolveAndValidateActor (or already known at tools/list time for direct actor tools).

Why

Split out from #1256 per review: a remote pre-flight validate-input call can't reduce failures (same validator either way), but the existing fallback message on a real start()-time input rejection points the agent at the wrong problem. This fixes just that response shape.

Direct actor tools (actor_executor.ts) hit the same platform rejection via their own actor.start() call but had no interception at all — extended after the initial call-actor fix landed. Kept as independent inline logic in each of the two call sites rather than a shared helper: measured a shared module at +17 net lines to guard a duplication that's structurally capped at exactly these two places (the only two spots in this codebase that start() an Actor against user-supplied input).

Testing

pnpm run type-check/lint/test:unit/format/check:agents clean (93 files, 1361 passed, 1 skipped, zero regressions). Unit tests cover: schema+message returned (not the generic fallback) when inputSchema is available, the platform message alone when it isn't, the direct-actor-tool executor delegating instead of throwing, any other start() error rethrown unchanged, and the isActorInputValidationError predicate matching only the confirmed error type (not the sibling invalid-input-schema).

@github-actions github-actions Bot added t-ai Issues owned by the AI team. tested Temporary label used only programatically for some analytics. labels Aug 18, 2026
@MQ37
MQ37 marked this pull request as draft August 18, 2026 15:11
MQ37 added 4 commits August 19, 2026 09:22
call-actor/call-actor-widget fell through to a generic 'verify the
Actor name' message on a platform invalid-input 400 from start() —
pointing the agent at the wrong problem. Branch that path: for a
confirmed platform input-validation error, return the input-specific
message and the Actor's input schema, not the Actor-name/existence
hint. No extra network call — the schema was already fetched during
resolveAndValidateActor.

Discriminator: apify-core throws every start()-body validation
failure (bad JSON, wrong type, real-schema mismatch) as ApifyApiError
type 'invalid-input', status 400 — distinct from 'invalid-input-schema'
(a broken schema on the Actor itself, not a user input problem).

Written with AI assistance (Claude).
…ols too

Direct per-Actor tools (actor_executor.ts) hit the same platform
invalid-input 400 from actor.start() as call-actor, but errors there
fell through to the fully generic tool-call error mapper — no schema,
no input-specific message. Wrap just the start() call and intercept
the confirmed platform-validation case; everything else rethrows
unchanged, unaffected.

Inline, not a shared helper with call_actor.ts: verified LOC — a
shared module costs 17 more net lines and a new file to guard a
duplication that's structurally capped at these two call sites (the
only two places in this codebase that start() an Actor against
user-supplied input).
Same AJV-vs-platform-schema explanation was written out at 3 call sites; kept it at its one canonical home (isActorInputValidationError's JSDoc), call sites now just point at it.
@MQ37
MQ37 force-pushed the feat/call-actor-input-error-schema branch from 05c7b1b to 87865c6 Compare August 19, 2026 07:26
MQ37 added 3 commits August 19, 2026 11:01
expect(spies.startInput).toBeUndefined() checked a variable the test's own apifyClient override never wires up — always undefined regardless of code behavior.
Same respondUserError message array was duplicated in actor_executor.ts
and call_actor.ts for the isActorInputValidationError branch. Single-source
it next to isActorInputValidationError, mirroring the existing
buildPermissionApprovalTexts/buildActorNotFoundHint pattern.
@MQ37
MQ37 marked this pull request as ready for review August 20, 2026 09:14
@MQ37
MQ37 requested a review from jirispilka August 20, 2026 09:14

@jirispilka jirispilka left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks!

I made the two comments inline that I would like to have fixed.
Pre-approving.

One more thing: I re-ran the telemetry with Claude (details in #1266, reopened — #1257 closed it without the fix landing). 94.8% of platform schema rejections name a constraint that filterSchemaProperties strips, so the echoed schema mostly won't show why the input was rejected until #1266 lands. The platform's message is what carries the information here — still a clear improvement over "verify the Actor name…". FWIW: call-actor is 89% of these rejections, direct tools 5.4%.

Things I checked and won't nitpick: the as ActorExecutionResult cast (existing idiom there), the predicate scoping vs invalid-input-schema (matches API docs, tested), response size (same schema we already serve in tools/list).

Comment thread src/utils/apify_errors.ts
Comment on lines +41 to +48
export function buildInvalidInputTexts(actorName: string, errMsg: string, inputSchema?: ToolInputSchema): string[] {
return [
`Failed to call Actor '${actorName}': ${errMsg}`,
`Please ensure the input is correct and matches the Actor's input schema.`,
...(inputSchema ? [`Input schema:\n${wrapJsonText(inputSchema)}`] : []),
];
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move buildInvalidInputTexts out of apify_errors.ts. The module's own header says "Kept in one leaf module (imports only const + apify-client) so logging, telemetry, payments, and the tool layer can share them without import cycles." The PR adds import { wrapJsonText } from './encode_text.js' (runtime) and import type { ToolInputSchema } from '../types.js', so that comment is now false — and a text formatter isn't an error-classification predicate anyway.

Move it to src/utils/mcp.ts, next to respondUserError and getToolCallErrorUserText. That file already imports both ./apify_errors.js (line 5) and wrapJsonText (line 6), so it's zero new imports and no cycle. Move its test from tests/unit/utils.apify_errors.test.ts to tests/unit/utils.mcp.test.ts; the isActorInputValidationError predicate test stays put.

Comment thread src/mcp/tool_dispatch.ts
const executorResult = await actorExecutor.executeActorTool({
actorFullName: tool.actorFullName,
actorId: tool.actorId,
inputSchema: tool.inputSchema,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the direct Actor calls, the schema is returned but the LLM already has schema in the context. This will only duplicate it.

Drop the schema echo on the direct-actor-tool path. getToolPublicFieldOnly (src/utils/tools.ts:113) serves tool.inputSchema verbatim in tools/list, and MCP clients re-send tool definitions every turn — so on that path the echoed schema is guaranteed to already be in the agent's context at the moment it reads the error. It's duplicate tokens, not redundancy-as-insurance.

src/mcp/tool_dispatch.ts:244-245 — drop inputSchema: tool.inputSchema, keep actorId (telemetry needs it).
src/types.ts — drop inputSchema from ActorExecutionParams, keep actorId.
src/tools/actors/actor_executor.ts — call buildInvalidInputTexts(actorFullName, error.message) with no schema.

The direct path keeps both real wins: no more uncaught throw, plus the platform's own message. This also retires the reviewer's dead-branch question, and shrinks the diff.

call-actor/call-actor-widget keep the echo — there the schema genuinely isn't in context, since the tool takes an Actor name and a free-form input object.

Comment thread src/types.ts
/** Actor's platform ID — echoed into telemetry on a platform input-validation error. */
actorId?: string;
/** Actor's input schema — echoed back verbatim on a platform input-validation error. */
inputSchema?: ToolInputSchema;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we drop schema that is returned for direct tool calls:

Fix the two doc comments that overstate what's returned. src/types.ts says the schema is "echoed back verbatim" and the PR body says "the Actor's input schema".

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

Labels

t-ai Issues owned by the AI team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants