{"slug": "prefill-and-default-look-the-same-in-console-only-one-of-them-lets-an-agent-skip", "title": "prefill and default look the same in Console. Only one of them lets an agent skip your required field.", "summary": "A developer audited 23 Apify Store Actors and found that seven of the nine that can serve as second stages in agent-chained workflows carry a default value naming a concrete target (such as apify.com) even though the field is also listed as required. Testing showed that an Actor with a required field and no default rejects empty input with an error, while one with both a required field and a default silently runs against the default target, producing a well-formed but unrequested result. The developer applied a one-line schema fix and now distinguishes between the two input-schema settings.", "body_md": "I have 23 audit Actors on the [Apify Store](https://apify.com/store). They all do a version of the same thing: take a public record, check whether what it still claims is true, and write the verdict into a dataset.\n\nLast week I set out to prove they were unsafe for AI agents to chain together. I had a specific accusation in mind, I built the experiment to demonstrate it, and the experiment refused. What I found instead was a one-line schema fix that I have now applied, and a distinction between two input-schema settings that I had treated as interchangeable for months.\n\nChaining means running Actor A, taking something out of its output, and passing it into Actor B. For an agent to do that through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp), B has to be callable with a value that only A can supply. MCP is the Model Context Protocol, the interface that exposes your Actor to clients like Claude and Cursor as a tool they can call on their own.\n\nSo I read my own catalogue with a script instead of from memory. Of my 23 published Actors, **9** declare a URL or a domain in their input schema's `required` array. Counting every ordered pair where A emits the kind of identifier B requires, I get **61** possible chains: 36 joined by a URL, 25 by a domain.\n\nA tenth Actor requires an identifier too, and it is the interesting one. `dataset-to-github-issues` takes a dataset ID and a repository name. Nothing else in my catalogue emits either, so no agent can ever reach it from another Actor's output - it is a second stage with no possible first stage. I had not noticed that until the script told me.\n\nThen I looked at what those 9 do when the value never arrives.\n\nI had swept my catalogue for target-naming defaults once before and found them, so the count itself was not the surprise. Restricting it to the Actors that can actually be a second stage sharpens it: **seven of the nine carry a `default` that names a concrete target.**\n\n| Actor | field | default | \n|---|---|---|\n| `bulk-domain-checker` | `domains` | `[\"apify.com\"]` | \n| `dead-link-checker` | `domains` | `[\"docs.apify.com\"]` | \n| `domain-availability-checker` | `domains` | `[\"apify.com\"]` | \n| `http-status-checker` | `urls` | `[\"https://apify.com/store\"]` | \n| `seo-ai-visibility-auditor` | `startUrls` | `[{\"url\":\"https://apify.com\"}]` | \n| `sitemap-checker` | `domains` | `[\"apify.com\"]` | \n| `tech-stack-detector` | `domains` | `[\"apify.com\"]` | \n\nAll seven are also listed in `required`. I had assumed that word did some work.\n\nMy accusation was going to be this: in a two-stage workflow, an agent that drops the value between stages gets no error. The second Actor runs on `apify.com`, returns a well-formed dataset, and the agent reports a confident answer about a website the user never asked about. A wrong answer with no failure attached to it.\n\nBefore involving any agent, I called two of my own Actors with an empty input from the command line.\n\nThe first, [`seo-audit-tool`](https://apify.com/aiqlabs/seo-audit-tool), has `urls` in `required` and **no** `default`:\n\n```\napify call aiqlabs/seo-audit-tool --input '{}'\nRun: Calling Actor aiqlabs/seo-audit-tool (ZjU3YtyaWgqpNriNj)\n\nError: Input is not valid: Field input.urls is required\n```\n\nNo run was created. No dataset, no compute, no partial result to misread. The platform named the missing field and stopped.\n\nThe second, [`http-status-checker`](https://apify.com/aiqlabs/http-status-checker), has `urls` in `required` **and** a `default`:\n\n```\napify call aiqlabs/http-status-checker --input '{}'\nINFO  Checking 1 URL(s) with 8 in flight. HTML analysis: on.\nINFO  Done. 1 URL(s): 0 error, 0 warning, 0 info, 1 clean.\n\nApify call result: SUCCEEDED\nRun ID: sMLok9a5KMSSdtnD1\n```\n\nI sent nothing. It audited something. Reading the input the platform stored for that run shows what:\n\n```\n{\"urls\":[\"https://apify.com/store\"],\"analyzeHtml\":true,\"respectRobotsTxt\":true,\n \"robotsAgent\":\"Googlebot\",\"onlyIssues\":false,\"maxConcurrency\":8,\n \"requestTimeoutSecs\":20,\"maxRedirects\":8,\"slowResponseMs\":3000,\"maxUrls\":10000}\n```\n\nSame platform, same account, same minute, same field name, both fields marked `required`. One call was refused and one was silently completed. The only difference is the `default`.\n\nThere is a second thing in that pair worth pulling out. `seo-audit-tool` does carry a `prefill` on `urls` - two example URLs that appear in the Console form. It was still rejected. `prefill` populates the form for a human; `default` is substituted for whoever omits the field.\n\nThe [input schema specification](https://docs.apify.com/platform/actors/development/actor-definition/input-schema/specification/v1) is not vague about this. On `prefill`:\n\nthis field is only used in the user interface but does not affect the Actor functionality and API\n\nand, in the same paragraph:\n\nthe Prefill value won't be used by existing integrations such as Actor tasks or API calls, but the Default will be if specified\n\nOn `default`, it names the callers explicitly: the platform passes the value when the user omits it \"via any means (API, CLI, scheduler, or user interface)\". An agent calling through the MCP server is one of those means.\n\nI had read that page. I read it as a description of two features and used both, on the same field, for months.\n\nThere is a detail in `http-status-checker` that makes the gap concrete. The two settings on `urls` do not even hold the same value:\n\n```\n\"urls\": {\n  \"prefill\": [\"https://apify.com/store\",\n              \"https://apify.com/this-page-does-not-exist\",\n              \"http://apify.com/\"],\n  \"default\": [\"https://apify.com/store\"]\n}\n```\n\nThree URLs for the form, one URL for anyone who omits the field. I wrote both, months apart, and never put them side by side. A caller that skips `urls` does not get the demo I built - it gets a shorter thing I stopped thinking about, and no part of the interface ever shows the two together.\n\nTo test the chain I needed a question that one Actor cannot answer on its own. That took two attempts.\n\nMy first idea was `sitemap-checker` into `http-status-checker`: enumerate a site's sitemap, then check the URLs. It is a bad example, because `sitemap-checker` has `checkUrlStatus` set to `true` by default and already requests every URL. My second idea was `hacker-news-link-rot` into `domain-availability-checker`, and that fails the same way - the first Actor already emits `domainRegistrationStatus`.\n\nThat is worth pausing on. I had built every one of these Actors to answer a question completely, which is right for a standalone Store listing and is exactly what makes them poor chain stages. Composable and self-contained are not the same design, and I had only ever aimed at one of them.\n\nThe question I settled on works because it needs HTML analysis, which the sitemap Actor does not do:\n\nAmong the URLs listed in crawlee.dev's sitemap, which ones are marked noindex, and which ones are soft 404s?\n\nA [noindex](https://developers.google.com/search/docs/crawling-indexing/block-indexing) page is one that tells search engines to keep it out of the index; a [soft 404](https://developers.google.com/search/docs/crawling-indexing/http-network-errors) is a missing page that returns 200 anyway. Both need the HTML of the page. `sitemap-checker` emits 11 fields and none of them is either. `http-status-checker` has both, behind an `analyzeHtml` switch that is on by default. The chain is necessary, and [crawlee.dev](https://crawlee.dev) is a real documentation site with a large sitemap, which makes it a fair test.\n\nI gave that question, word for word, to four fresh agents with both Actors exposed through the Apify MCP server, and no other instructions. I did not tell them to be careful, and I did not mention inputs or defaults - in an earlier experiment I learned that asking an agent to count its rows is the same as telling it where to look.\n\nThen I ignored what they told me and read what the platform stored.\n\n| stage-2 run | `urls` passed | first entry | \n|---|---|---|\n| `p5qwOESMQB0Vwj0ls` | 500 | `https://crawlee.dev/js/api/3.11/playwright-crawler/...` | \n| `NPM7mW5oC848wn2ZL` | 174 | `https://crawlee.dev/js/api/3.16/puppeteer-crawler/...` | \n| `3fO6vTbKnLlkDwGnw` | 500 | `https://crawlee.dev/blog` | \n| `H0HkakmGE2bfkAdWU` | 400 | `https://crawlee.dev/js/api/3.15/core/interface/HttpResponse` | \n| `2cJHKXtsrapw9aikV` | 400 | `https://crawlee.dev/js/api/3.14/browser-pool/class/PuppeteerPlugin` | \n| `oGbGCrfPEM4uDuOBY` | 400 | `https://crawlee.dev/js/api/3.12/puppeteer-crawler` | \n| `sDQnaUb7NxX9iz88B` | 400 | `https://crawlee.dev/js/api/3.11/core/class/SystemStatus` | \n| `QDpb6PG685ptVthUN` | 400 | `https://crawlee.dev/blog` | \n| `bB20ynAk9ymZafaiY` | 724 | `https://crawlee.dev/js/api/3.14/utils/function/downloadListOfUrls` | \n\nNine stage-2 runs. Every one carries real crawlee.dev URLs. Not one carries `https://apify.com/store`. They also batched - 174 to 724 URLs per call - rather than making one call per URL.\n\nThe trap I had spent a day setting never closed.\n\nThe mechanism is in the result once you stop arguing with it. A `default` stands in for a value the caller never had. Inside a chain, the agent produced that value itself one step earlier and is still holding it. The omission the default is waiting for does not occur.\n\nThat reconciles this with the case where the same defect does bite. When an agent calls one of these Actors cold - no preceding step, nothing in hand - the missing field is real, the platform fills it, and the run proceeds on a target nobody chose. The danger lives at the entry point of a workflow, not in the joins.\n\nI would rather publish that than the article I planned, because it changes where you should spend attention. If you are worried about agents mangling your Actor, the risk is concentrated in the first call, not in the handoffs.\n\nA reviewer would find this, so I will say it first.\n\nThe question I picked has a null answer. Here is the summary record from one 500-URL batch:\n\n```\n{\"checked\":500,\"severity\":{\"ok\":500,\"info\":0,\"warning\":0,\"error\":0},\n \"headline\":{\"broken\":0,\"soft404s\":0,\"noindexOnLivePages\":0,\"metaRefreshRedirects\":0}}\n```\n\ncrawlee.dev has no soft 404s and no noindex pages in those URLs. \"Which ones are noindex?\" answers to \"none\". An agent that had quietly audited `apify.com/store` instead would probably also have answered \"none\".\n\nSo the agents' answers cannot tell a careful run from a careless one here. Only the stored inputs can. That is the one thing I got right by accident of habit: I built the measurement on records the platform keeps, not on what the agents said about themselves. It mattered more than I expected - **all four agents finished without returning a report to me at all**, and every number above survived that because none of them came from an agent.\n\nA stronger version of this trial would use a site with known noindex pages, so a substituted target produces a *different* answer rather than the same one. If you repeat this, pick your target site for that property.\n\nFor any input field that names *what* to work on - a URL, a domain, a repository, an account - delete the `default` and keep the `prefill`.\n\n```\n\"urls\": {\n  \"title\": \"URLs\",\n  \"type\": \"array\",\n  \"editor\": \"stringList\",\n  \"prefill\": [\"https://apify.com/store\"]\n}\n```\n\nwith `\"required\": [\"urls\"]` alongside it. The Console form still opens with an example filled in, so nothing changes for a human clicking through your Actor. What changes is that a caller who omits the field now gets `Field input.urls is required` instead of a clean dataset about somebody else's website.\n\nThe specification anticipated this too. Its guidance for `required` reads:\n\nUse for fields that don't have a reasonable default and MUST be entered by the user (e.g., API token, password)\n\nFields that don't have a reasonable default. Seven of mine had both, which is a combination the sentence quietly rules out. Marking a field `required` and then supplying the answer yourself is not a stricter setting; it is two instructions that cancel.\n\nKeep `default` for settings that describe *how* to work - concurrency, timeouts, depth limits, feature switches. Those are genuinely safe to assume, and an empty call that inherits them is behaving correctly.\n\nThe distinction is worth stating as a rule: `default` on a method is a convenience; `default` on a subject is a substitution.\n\nI wrote the catalogue sweep as a script so I would stop trusting my memory of my own schemas. It reads every local `.actor/` directory, classifies each input and output field into an identifier kind, and reports which of your Actors can be a chain stage and which of them will accept an empty call:\n\n```\nnode composability.mjs           # summary and pair matrix\nnode composability.mjs --rules   # print the classifier so you can audit it\n```\n\nThe `--rules` flag exists because the classifier is regular expressions over field names, and a regex table you cannot see is a number you should not trust. Mine misfired the first time I ran it: it reported that `github-repository-audit` requires no identifier, which is nonsense for an Actor that audits repositories. The cause was real and boring - that schema has no `required` key at all, so every field is optional and the defaults decide everything.\n\nIf you want a single check with no script, call your own Actor with an empty input and see what happens:\n\n```\napify call <your-actor> --input '{}'\n```\n\nIf it succeeds, look at what it audited. That is what an agent gets when it forgets.\n\nI have not stripped the defaults from all seven Actors yet. The measurement says the harm is concentrated in cold calls, and I want to measure that path on these specific Actors before I change seven live listings - the last time I acted on a confident prediction here, the prediction was wrong twice.\n\nWhat I have changed is the rule I apply to new fields, and the check I run before publishing. Both of them came out of an experiment that failed to prove its own thesis, which is becoming a habit I am not entirely unhappy about.", "url": "https://wpnews.pro/news/prefill-and-default-look-the-same-in-console-only-one-of-them-lets-an-agent-skip", "canonical_source": "https://dev.to/apify/prefill-and-default-look-the-same-in-console-only-one-of-them-lets-an-agent-skip-your-required-4bc", "published_at": "2026-09-11 05:18:13+00:00", "updated_at": "2026-09-11 05:56:04.000405+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-products"], "entities": ["Apify", "Apify Store", "Model Context Protocol", "Claude", "Cursor", "seo-audit-tool", "http-status-checker", "dataset-to-github-issues"], "alternates": {"html": "https://wpnews.pro/news/prefill-and-default-look-the-same-in-console-only-one-of-them-lets-an-agent-skip", "markdown": "https://wpnews.pro/news/prefill-and-default-look-the-same-in-console-only-one-of-them-lets-an-agent-skip.md", "text": "https://wpnews.pro/news/prefill-and-default-look-the-same-in-console-only-one-of-them-lets-an-agent-skip.txt", "jsonld": "https://wpnews.pro/news/prefill-and-default-look-the-same-in-console-only-one-of-them-lets-an-agent-skip.jsonld"}}