feat: Add get-actor-version tool - #1450
Merged
Merged
Conversation
Adds get-actor-version, the read side of the source tools that replace push-actor and pull-actor, in a new source category that is not enabled by default. It returns a version's metadata, a file manifest (path, decoded size, hash, format), a revision for the whole file set, and the content the caller asks for within MAX_INLINE_BYTES. Without paths it returns every text file only when all of them fit, so one large file cannot crowd out the rest; paths: [] gives the listing only; named paths fill the limit in order, and a single large text file can be read in line ranges. Environment variables come back as names and isSecret only. The hash is the first 16 hex characters of the SHA-256 of the file's bytes, and the revision hashes the sorted path and hash lines, so both are the same for a version stored inline and one stored as a zip. Zip-stored versions are read only from a key-value store record on the session's own API host, with the caller's token. The zip is parsed from its central directory with no new dependency, because apify push writes zeros for sizes in local headers. Archives over 50 MiB, over 10,000 entries, or over 64 MiB uncompressed are refused, as are encrypted, zip64, symlink, and unsafe or duplicate names, and every entry's length and CRC-32 are checked after inflating. Git, gist, and outside zip sources return only their URL, without its query string.
Zip reading: - Refuse an archive whose end record declares fewer entries than the central directory holds, and an entry whose local header names another file or method, so the files reported match what other zip readers see. - Check entry names for length, NUL, absolute paths, and '..' first, so no refusal message repeats an overlong name. - Skip a root folder entry such as './' instead of refusing the archive. - Detect text with isUtf8 and decode only the files that are returned, so a listing holds only the inflated bytes the 64 MiB cap bounds. Inline files: - Read a file stored without format as TEXT and one without content as empty, as the build worker does, instead of failing or breaking the output schema. - Normalize inline paths the way the zip reader does, so the same files give the same manifest and revision however they are stored. - Return a UTF-8 file stored as BASE64 as text, as a zip read does. The manifest keeps the stored format. Results: - A line over the 256 KiB limit no longer fails the call. The file goes to omittedPaths and the summary names the startLine that skips it, or says a single-line file cannot be read by lines. - The summary no longer tells the caller to name a base64 file too large to return; it points to the Apify CLI instead. - Remove the query string from Git and gist URLs too, and http user and password from every URL, before returning them or hashing the revision. - Word the outside zip summary without claiming the source is not on Apify. - The description says the API hides the source of most Actors the caller cannot modify, and gives the macOS hash command. Tests cover each rule and the boundaries that no test pinned before: hidden sources for URL types, a record URL on another host, local extra fields that differ from central ones, byte order of the manifest, base64 files next to text at the limit, and the entry count, size, CRC, and encryption flag edges.
This was referenced Sep 25, 2026
The Apify API cannot read or change single files of an Actor's source, so the version GET returns every file and a zip-stored version is downloaded and unpacked whole, even for one file. TODO comments mark both places for when such an API exists.
…y it Remove the zip reader, its test helper, and its tests. A version stored as a zip in a key-value store of this API is now refused as a user error; a zip at an outside URL is still reported by its URL. A TODO marks where zip reading comes back and the library planned for it. Other simplifications, with the same output data: - One summary function replaces the format helpers. - A line over the limit is reported only when nothing of the range fits. - No Apify CLI hint for files too large to return. - Paths sort in plain JavaScript string order instead of UTF-8 byte order, for the manifest and the revision lines. - One hash helper, and the manifest builder and folder check live in source_files.ts.
- Say that the tool does not download zips when it reports an outside zip URL, since a URL on the session's own API host is not outside the Apify API. - Describe the path normalization correctly: empty and `.` segments are dropped as the build worker does, and backslashes become `/`, which the build worker does not do for inline names. - Pass only the API base URL to the version reader, since it no longer calls the API, and fix comments that still mention the source store. - Stop exporting the hash helper and the format type, and read an entry's name, format, and content in one destructuring. - Test that a BASE64 file with a text extension and invalid UTF-8 comes back as base64, that a byte order mark is kept, and that the last of two entries with the same path wins.
Narrow the tool to what the spec asks for: - Without paths, return the file list only. Drop pathPrefix and the read-all-text-files default. - Keep basic line ranges; a range over the limit goes to omittedPaths. Drop the automatic range and the long-line handling. - Refuse every version not stored as files with one message that names its URL without credentials. Drop Git, gist, and zip reporting, the URL revision, and the split of Git URLs. - Read the version from the Actor GET, which already hides the source the same way. Drop the second GET, the 4xx catch, and the no-versions branch. - Drop env var names, the stored format, the source type, and the build tag from the output, and keep the text result to one sentence. - Normalize stored names with posix.normalize, as the build worker does.
Hide the password of a URL in any scheme, not only http and https, so an ssh:// Git URL with a password no longer shows it in the refusal. The SSH user stays. Test that a BASE64 file with a binary extension comes back as base64 even when its bytes are valid UTF-8.
# Conflicts: # tests/unit/tools.mode_contract.test.ts
# Conflicts: # README.md # src/tools/registry.ts
DaveHanns
marked this pull request as ready for review
September 30, 2026 06:41
5 tasks
1 task
1 task
5 tasks
… get-actor-version - Compare the text block with structuredContent for file contents, base64, CRLF text, and a line range. - A file stored without format is text even with a binary extension. - Binary extensions match in any case, and base64 comes back as stored. - A path with no file uses none of the byte limit. - The description states the spec's 256 KiB, not the shared constant. - A version with no files is an empty listing, not a hidden source. - A version without a number is left out of the several-versions message. - A sub-resource with a username but no name is a missing Actor. - buildFilesRevision sorts its input by path itself.
MQ37
approved these changes
Sep 30, 2026
MQ37
left a comment
Contributor
There was a problem hiding this comment.
Approve, with one thing to fix before merging.
I tested this branch end to end against the live Apify API. It works. File content is byte-exact, hashes and revision are correct and stable, and line ranges are right. The tool uses only the session's own token, and envVars are never returned.
Fix before merge
- A file or range over 256 KiB is omitted with no explanation.
- Repro:
{"actor":"<actor>","versionNumber":"0.0","paths":["src/big.log"]}with a file over 256 KiB. - Result:
isError=false,contents:[],omittedPaths:["src/big.log"], and the only text is "Read version 0.0 of …". A caller that reads the text alone can think it got the file. startLineon its own, or a largelineCount, does the same.- Please name the omitted paths in the summary, with the limit and a hint to use
startLine/lineCount. - The same goes for
notFoundPaths. Directories likesrc/and paths like./xonly show up there.
- Repro:
Follow-ups, not blocking
- Validate
actoras an ID orusername/name. It goes into the API URL unescaped, so../is normalised. It's GET-only under the caller's own token and gave no leak in my probes. - Every read repeats the full file listing, and
content[0]duplicatesstructuredContent. - Refusals give no next step, and the zip message shows
TARBALLand the internal record URL. - Unknown parameters are silently dropped and types are coerced (
src/utils/ajv.ts). That isn't this PR, so I'll open a separate issue.
Nits (optional)
- Build
lineRangeonce instead ofisLineRangepluslineRange. - Make
contenteager instead of the lazyreadContentclosure. - Trim the adm-zip TODO and the long docblocks.
- Merge the near-duplicate tests into
it.each. - Replace
files.findin the loop with a Map. - Use one template literal for the not-found message instead of
+.
l2ysho
approved these changes
Oct 1, 2026
Build the line range once, keep each file's content instead of a function that builds it, look files up in a Map, write the Actor-not-found message as one template literal, and shorten the TODOs and long doc comments.
The text said only "Read version <n> of <fullName>.", so a caller reading the text alone could take a file that did not come back as read. It now names the files left out over the limit with a next step (fewer lines for a line range), and the paths with no file.
Stored names are normalized, so ./src/main.js found no file although the listing has src/main.js. Requested paths are now normalized the same way, paths to the same file count once, and a path with no file is reported as the caller wrote it.
A version not stored as files was refused with its source type and URL. The refusal now says where the files are and what to use instead: the Git repository or the gist, with credentials still hidden from the URL. A zip version is said to be one apify push stores for sources over 3 MiB, and its internal URL is no longer shown. Other source types are named as not supported. The wording fits tools that read and tools that write.
The base64 detection, line range, line range refusal, and missing-Actor tests differed only in input and expected output. Every case is kept.
1 task
Collaborator
|
Follow-up for two text defects found with mcpc on the merged tool (0.17.2): #1470. Both came in with the commits after the approvals (41285bc, 1b372e9). Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
get-actor-versionreads an Actor version's files: the file list with a size and a hash per file, arevisionfor the whole set, and the content of the files asked for. It is the first tool of a newsourcecategory, which is not enabled by default.Why
It is the read half of the Actor source tools spec (#1452). Closes #1460. The write tools in the next PRs use its hashes and
revisionto refuse changes that conflict with a newer save.How
actor(ID orusername/name),versionNumber(defaults to the only version),paths, andstartLinewithlineCountfor one path.paths, it returns the file list only. Withpaths, it returns those files in order within 256 KiB. Requested paths are normalized like stored names, so./src/main.jsfindssrc/main.js.omittedPaths, and paths with no file tonotFoundPaths. The text summary names both and says what to do next, for a caller that reads only the text.utf8and binary files asbase64. A UTF-8 file stored as BASE64 (apify pushstores.tsfiles that way) comes back as text.hashis the first 16 hex characters of the SHA-256 of a file's bytes.revisionhashes the sorted paths and hashes, so neither depends on how the version stores its files.Testing
Unit tests cover each point above, the refusals, and schema conformance.
type-check,lint,format,test:unit, andcheck:agentspass. A reviewer tested it end to end against the live API.Notes
create-actorandupdate-actor-version(feat: Add create-actor and update-actor-version tools #1453) stack on this PR.source_files.tsexports a few helpers that only feat: Add create-actor and update-actor-version tools #1453 imports.AI disclosure: implemented with Claude Code; awaiting human review.
🤖 Generated with Claude Code