Skip to content

feat: Add create-actor-version and delete-actor-version tools - #1454

Open
DaveHanns wants to merge 85 commits into
feat/update-actor-versionfrom
feat/actor-version-create-delete
Open

DaveHanns wants to merge 85 commits into
feat/update-actor-versionfrom
feat/actor-version-create-delete

Conversation

@DaveHanns

@DaveHanns DaveHanns commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

What

Two tools in the versions category (renamed from source in #1453):

  • create-actor-version adds a version to an existing Actor, from files or as a copy of another version (copyFromVersion).
  • delete-actor-version deletes a version.

Why

The API's version update cannot create a version, so without this tool an agent cannot add 0.2 next to 0.1. A copy gives an agent a working version to change and build without touching the one in use. Part of the Actor source tools spec (#1452). Closes #1475.

How

  • create-actor-version: actor, versionNumber, either files or copyFromVersion, buildTag, and autoBuild. One POST creates the version.
  • A copy takes the other version's files, its non-secret env vars, and applyEnvVarsToBuild. The files go back exactly as the API returned them, folders and entries without a format or content included. The API does not return secret values, so secrets are left out, and warnings names them. Without autoBuild, the warning says they can be set on the new version in Apify Console, and that only a build made after that includes them. When autoBuild started a build, the tool keeps it, and the warning says that build runs without them and, if it succeeds, takes the buildTag when one was given. Only versions stored as files can be copied.
  • buildTag has no default and is never copied, so builds of a working copy never move a tag such as latest.
  • The actor argument of both tools takes an ID, a full name, or a bare name resolved against your own Actors, through the shared fetchActor from feat!: Add create-actor and update-actor-version tools #1453.
  • Files refused before anything is written, as in create-actor: a path with a control character (U+0000 to U+001F or U+007F) or a lone UTF-16 surrogate, a path outside the Actor root or at a folder path, the same path twice, one path used as both a file and a folder, base64 that does not decode without loss, and text with a lone UTF-16 surrogate. The platform accepts all of these, and the build then fails or stores a corrupt file.
  • autoBuild (default false) starts a build without waiting for it. A build that cannot start is reported in buildError, and the version stays. A cancel that arrives after the POST, including during the Console link lookup before the build start, keeps the version and starts no build. create-actor-version has destructiveHint: true and openWorldHint: true: with buildTag and autoBuild, the build it starts moves an existing tag, as build-actor does. delete-actor-version starts nothing and keeps openWorldHint: false.
  • Logged arguments: create-actor-version sets redactArgs as create-actor does. The logged copy keeps the Actor, versionNumber, copyFromVersion, buildTag, autoBuild, and each file's path and encoding, and replaces file content with [REDACTED]. delete-actor-version takes no file content.
  • A retried create: apify-client can retry the POST, and the platform then refuses the version number as taken. The tool says an earlier attempt of the same call may have created the version, and that if it holds the files sent, this call created it. It names get-actor-version, which reads the files, only when the session has that tool. For a copy, it names the files of the copied version, not the files the call sent, and it repeats the warning about the secret env vars that were left out.
  • delete-actor-version: actor, versionNumber, and an optional expectedRevision. When expectedRevision is given, the delete is refused unless the version's revision matches, compared as update-actor-version compares it. A version not stored as files, or one whose source the API hides, has no revision, so the delete is then refused. It reads the Actor first, because apify-client treats a 404 on delete as success. The version's builds and their tags stay. The tool description says a deleted version cannot be restored, that a build of the version that is still running keeps running and takes its tag if it succeeds, and to delete a version only when the user asks.
  • structuredContent.tags lists the tags that point to a build of the deleted version, empty when none. When a tag points to a build of the deleted version, the reply also names the tag and says runs with it still use that build until the tag points to another build, such as one of another version made with that tag. With several such tags, it names one build per tag, since build-actor takes one tag. It names build-actor only when the session has that tool. The tool reads where each tag points from the Actor it already fetched, and matches the build number on the version number followed by a dot, so deleting 0.1 does not name a tag on build 0.10.1. It does not use the version's build tag: a tag stays on the build it was last given to, which can be a build of another version.
  • A version from files and a delete work whatever the Actor's other versions are stored as. Only a copy needs a version stored as files.
  • Served text follows TOOL_GUIDELINES.md and states facts: for example, the files-or-copy refusal says "The call takes exactly one of files and copyFromVersion."
  • The last version: the platform refuses to delete it, and the tool says it is the Actor's only version and that an Actor keeps at least one version.
  • Left to the platform: a version number already taken, the limit of 10 versions per Actor, the limit on the combined size of all versions, and permissions. A permission error reaches the agent as it does for any other tool. The other refusals come back with the platform's message and error type, without the tool-call engine's hint that the token may lack access, since none of them is about access.
  • get-actor-version (on master) returns the version's buildTag in structuredContent, and its summary names it, for example "Read version 0.2 (build tag beta) of john/my-actor." Without it, an agent comparing two versions could not tell which one latest builds.
  • buildSourceFileEntries moves from create_actor.ts to source_helpers.ts, so both create tools share it. So do respondAfterCreate (the result after the POST) and respondToCreateError (the refusal of a retried POST). It checks paths with a set and a map (from feat!: Add create-actor and update-actor-version tools #1453), so 20,000 files take milliseconds, not 8 s.
  • The revision comes from the shared function, which feat!: Add create-actor and update-actor-version tools #1453 changed to the first 16 hex characters of the SHA-256 of the JSON array of [path, hash] pairs, sorted by path, so it still matches what get-actor-version reads.

Testing

Unit tests cover versions from files and copies (the same manifest and revision as get-actor-version, the copied entries, env vars, and no build tag), refused files and source types, the files-or-copy rule, autoBuild and buildError, a taken version number on the first try and on a retry, the version limit and the size limit (the platform's message and error type, status 403, no token hint), the last-version refusal, a permission error passed on to the engine, a missing version on delete, cancellation, and schema conformance. A cancel during the POST is tested with autoBuild. They also cover the secret env var warning without autoBuild, after a build that could not start, and after a started build with and without buildTag, and no warning for a copy without secrets. The reply to a retried POST is tested for files, for a copy with secrets, and for a copy without. They cover naming get-actor-version and build-actor only when the session has them, the note about tags after a delete, and the tool description's lines on restoring a version, on a running build taking its tag, and on deleting only when asked. The delete cases include a tag that moved to a build of another version, a tagged version that was never built, a version without a build tag whose build got a tag from build-actor, two tags on builds of the deleted version, and a tag on build 0.10.1 when deleting version 0.1. For the reviews of #1453, they also cover the logged arguments with a synthetic secret in file content and unknown keys, control characters and a lone UTF-16 surrogate in file paths, a cancel during the Console link lookup, 20,000 files checked within a second, a bare Actor name, and the open-world annotation. For the review of this PR, they cover destructiveHint, expectedRevision on delete (a match, upper case, a mismatch, a version not stored as files, a hidden source, and null), the tags and buildTag outputs, the last-version text, and versions-size-exceeded on the update-actor-version PUT. type-check, lint, format, test:unit, and check:agents pass.

Live integration cases in tests/test_kit/cases/versions.cases.ts run in CI against the platform on all three transports (stdio, streamable HTTP, and stateless HTTP):

  • create-actor-version from files, then get-actor-version reading back the same hashes and revision, with no build tag on the new version and the other version unchanged
  • a copy that stores the same entries (a folder and entries without format or content included), only the non-secret env var, applyEnvVarsToBuild, and no build tag, with a warning that names the secret env var but not its value, then the same call refused as taken
  • delete-actor-version deleting a version that has no builds, with no note about tags, then refusing a version the Actor no longer has, and the platform refusing to delete the last one
  • a copy of a version stored in a Git repository, refused with the repository URL shown without its credentials (added to the Git case from feat!: Add create-actor and update-actor-version tools #1453)

Each case uses its own test-source-* Actor, deletes it at the end, and starts no build.

Notes

  • Stacked on create-actor and update-actor-version (feat!: Add create-actor and update-actor-version tools #1453). This is the top of the stack.
  • Contract change for get-actor-version, which is on master: a new optional buildTag in its output schema and structuredContent, and a summary that names the build tag. The hosted server reads structuredContent, so its contract tests may need the field. The other changes here are to the new tools.
  • Request size on the HTTP servers: the dev server here and the hosted server parse JSON bodies with Express's default 100 KB limit, so a larger create-actor, create-actor-version, or update-actor-version call gets a 413 before the tool runs. fix: Accept request bodies up to 9 MB in the dev server #1611 (dev server) and apify/apify-mcp-server-internal#842 (hosted server) raise it to 9 MiB, tracked in HTTP servers refuse request bodies over 100 KB #1610; the internal one has to ship before these tools do.
  • A TODO comment marks where a copy reads every file of the other version and sends them all back, since the Apify API has no endpoint that copies a version. Once it has one, the platform should make the copy.

AI disclosure: implemented with Claude Code; awaiting human review.

🤖 Generated with Claude Code

Move the files input rules, the manifest builder, and the missing Dockerfile
warning from create-actor to source_helpers.ts, so create-actor-version can
use the same rules. create-actor behaves as before.
create-actor-version adds a version to one of your own Actors with one
POST: a copy of another version, new files, a Git repository, or an empty
version. A copy reads the other version and sends its source as stored,
so file formats, folder entries, and credentials stored with a Git or gist
URL are kept and no content passes through the model. Non-secret env vars
are copied with their values; secret ones cannot be read, so their names
are returned. Zip-stored versions cannot be copied yet. buildTag has no
default, so a working copy never takes over latest.

delete-actor-version reads the Actor first, refuses a version it does not
have, and refuses its last version, also when the platform does. The
version's builds and their tags stay.

Both tools are in the source category.
- The refusal for a zip-stored source version no longer suggests a bare
  apify push --version, which takes the tag from .actor/actor.json (often
  latest) and builds right away. It points to files or gitRepoUrl, and
  gives the CLI command with a build tag of its own.
- The refusal for an existing versionNumber leads with a free number
  after the highest version. update-actor-version is offered only for a
  caller who meant that version. After a race, the list includes the
  requested number.
- The result warns when the new version turns Standby on for the whole
  Actor: the platform does that when .actor/actor.json sets
  usesStandbyMode and Standby is off.
- update-actor-version's refusal of a missing version names
  create-actor-version when the session has it.
- Share the Git or gist source text, the call size check for new files,
  the sent files warnings, and the path, duplicate, and entry rules for
  files and replaceFiles through source_helpers.ts.
- Type the version POST body instead of casting it.
- Mark the delete-actor pointer for the last version as TODO(#1439).
- Tests: the copy test compares against its own copy of the stored
  files, and the input schema checks match tool names with any
  punctuation, in delete-actor-version too.
…feat/actor-version-create-delete

# Conflicts:
#	src/tools/source/create_actor.ts
#	src/tools/source/source_helpers.ts
A copy reads every file of the source version and sends them all back, because the Apify API cannot copy a version or apply atomic changes to an Actor's source. A TODO marks the place for when it can.
…te-delete

# Conflicts:
#	src/tools/AGENTS.md
#	src/tools/source/source_helpers.ts
…te-delete

# Conflicts:
#	src/tools/source/create_actor.ts
…te-delete

Take the simplified get-actor-version, create-actor, and update-actor-version,
and move create-actor-version and delete-actor-version onto the base helpers:
fetchActor and resolveVersion instead of the own-account check, extractSourceFiles
for copies, buildSourceFileEntry for sent files, and respondAfterWrite for the
build. Copies of Git, gist, and zip versions, gitRepoUrl, and the file rules the
base dropped go with those helpers.
create-actor-version takes files or copyFromVersion and sends one POST. A copy
takes the other version's stored files, its non-secret env vars, and
applyEnvVarsToBuild from the Actor GET, never its build tag, and is refused for
a version not stored as files. The version-exists check and its rewording, the
Standby warning, the secret env var list, empty-version handling, and the
summary variants are gone; the platform's own errors reach the caller.

delete-actor-version keeps the Actor read before the DELETE, since apify-client
reports a delete of a missing version as done, and leaves the last-version
refusal to the platform.
…-version simplification

create-actor-version now refuses a call with neither files nor
copyFromVersion before any request, as it already did for a call with both.
The platform accepts an empty file list and creates an empty version, and
with autoBuild it would also build that version, which the tool does not
offer.
create-actor-version built the entries of a version from files with no
check, so a path outside the Actor root, a folder path, the same path
twice, a file under another file, text sent as base64, and text with a
lone UTF-16 surrogate were POSTed and reported as created. The platform
stores them, and the build then fails or writes a corrupt file.
create-actor-version now builds the entries with the checks create-actor
runs, moved to source_helpers.ts, and refuses before the POST. A copy
still sends the other version's entries as stored.
…e version

apify-client retries the version POST after a network error, a timeout,
a 429, or a 5xx. When the platform saved an attempt it did not answer,
the retry fails with version-already-exists, and create-actor-version
reported the version it had just created as taken. A refusal of the
number on a retry now says that an earlier attempt may have created the
version and that it should be read before calling create-actor-version
again, as create-actor does for a name.
…ut the token hint

The platform refuses a version number the Actor already has
(version-already-exists) and the deletion of an Actor's last version
(too-few-versions) with a 403. create-actor-version and
delete-actor-version rethrew both, so the tool-call engine added "The
resource may be private or your token may lack access." and recorded
an AUTH failure. Both refusals now come back as a soft failure with the
platform's message and error type, and no hint. Other API errors still
go to the engine.
With buildTag and autoBuild, the build it starts moves an existing tag, as build-actor does.
The 403 mapping is shared by the source tools; the PUT is the write most likely to hit the size limit.
The platform's too-few-versions refusal now reads as the Actor's only version. Only delete-actor-version can get it, so the shared source tool mapping no longer lists it.
When given, the delete is refused before the DELETE unless the version's files have that revision, computed as get-actor-version and the write tools compute it and compared ignoring case. A version not stored as files has no revision, so expectedRevision is refused for it. A null or empty expectedRevision counts as not given.
structuredContent gets buildTag, left out when the version has none, and
the summary names it, so a caller can tell which version builds as
latest and which as beta.
…-version

structuredContent gets tags, the tags its text already names, as an
array that is empty when no tag points to a build of the version.
…ate tools

create-actor and create-actor-version built the same result after the POST and the same refusal of a taken name or number on a retried POST. Both now come from source_helpers.ts, with the served text unchanged.
…sertion

A type guard on the check that the call gives exactly one of files and copyFromVersion types files as defined when copyFromVersion is not given.
throw new UserInputError(`${label} repeats the path ${entry.name}; each path can be sent only once.`);
}
const folders = listFolders(entry.name);
const collision = folders.find((folder) => filePaths.has(folder)) ?? firstFileByFolder.get(entry.name);

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.

from codex:

Suggestion, separate follow-up: validateNewFilePath and buildSourceFileEntries both enforce file-versus-folder collisions. The former copies and scans the paths for each new file; the latter uses a set and map. Could we share the collision-checking logic while preserving ordered update operations and explicit folder entries? I haven’t found a correctness failure in either implementation.

@jirispilka jirispilka added the t-builders Issues owned by the Builders team. label Oct 16, 2026

@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.

Again, sorry, did not have time to go through it all.

Did some testing with an agent only 😊

Tested #1454 at 8ac7274 end to end through mcpc, using GPT-6 Luna as the testing agent and the Apify CLI to verify platform state.

Creation, reading, copying, write/edit/delete operations, revision and hash checks, version deletion, tag retention, and last-version protection passed. Failed update preconditions left the stored files unchanged. Real builds and an Actor run also succeeded.

Two observations:

  • An unchanged update with autoBuild: true starts a build. This reproduces the no-op behavior already raised on #1453.
  • call-actor with an explicit callOptions.build failed with unknown-build-tag for latest when no latest build existed. A default call succeeded after creating that build. This needs separate investigation; it does not establish a defect in these source tools.

The source category is retired and loads no tools, so these cases would
get none of the tools they call.
The merge moved the tools to the versions folder, so the output schema import now sorts before them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t-builders Issues owned by the Builders team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add and delete Actor versions over MCP (create-actor-version, delete-actor-version)

4 participants