Skip to content

feat: validate actor.json paths before apify push uploads anything - #1503

Open
DaveHanns wants to merge 40 commits into
masterfrom
feat/push-validate-actor-json-paths
Open

DaveHanns wants to merge 40 commits into
masterfrom
feat/push-validate-actor-json-paths

Conversation

@DaveHanns

@DaveHanns DaveHanns commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

apify push checks none of the file-path fields in .actor/actor.json. The platform resolves them after the upload: a missing schema fails the build, a missing dockerfile silently falls back to a Dockerfile the user did not choose, and a missing readme or changelog falls back to the default README or CHANGELOG. This PR applies the worker's rules in the CLI before the login check and before the Actor is looked up or written. Problems that would fail the build or build the wrong image stop the push; a readme or changelog the platform recovers from only prints a warning.

Closes #1500. Part of apify/apify-core#28685. Builds on #1502 (merged): the check runs after the dockerContextDir resolver.

Changes

  • New src/lib/actor-paths.ts: checkActorPaths returns one problem (field, value, resolved path, reason, severity) per failing field. formatActorPathProblems builds the message for one severity, with a header that says what happens. push.ts prints warnings to stderr and continues, and prints errors and exits with code 5 (InvalidActorJson) when any exist. The login check now runs after these local checks, so a broken Actor fails the same way with or without a login.
  • Fields: dockerfile, readme, changelog, input, output, webServerSchema, the storages schemas, plus inputSchema and outputSchema. The worker copies a set alias over input or output, so the alias wins and the base key is skipped. Inline objects and empty strings (unset on the worker) are skipped.
  • Rules: relative to .actor/ and inside the upload root, no backslash (the platform resolves paths with forward slashes), an existing file, and among the files push uploads. The first rule reads the value as the Linux worker does, where a backslash is part of a name, so an absolute or escaping path stays an error even with a backslash in it. dockerfile, readme, and changelog match case-insensitively, as the worker finds them by a directory listing; schemas match exactly.
  • Severity: for readme and changelog, a missing file (a dangling symlink included), a file push does not upload, a case mismatch with the uploaded file, and a backslash are warnings. An absolute path, a path outside the upload root, and a directory stay errors, as the worker fails the build on them. dockerfile and schema references are errors in every case.
  • Symlinks count by their own path: push reads through a link and uploads the content there, so a link to a file outside the upload root passes when push sends it.
  • When dockerContextDir points inside the Actor folder, dockerfile must lie inside that context: the worker runs BuildKit with --local dockerfile=<context dir> and filename=<Dockerfile relative to the context>, so a Dockerfile outside it cannot be read by the build.
  • Without dockerfile and with a Docker context, the CLI applies the worker's default lookup: .actor/Dockerfile, then Dockerfile in the Actor folder, by any case, among the files push uploads. A default that .gitignore or .actorignore drops is skipped, as the platform never sees it. A found default must pass the same rules as a set dockerfile, so a default outside the context stops the push. With no default and a context other than the Actor folder, push stops too: the platform would build that context with its built-in Node.js Dockerfile. A context that is the Actor folder itself, or no context, keeps the built-in Dockerfile as before.

Tests

  • test/local/lib/actor-paths.test.ts: every rule, the aliases, empty strings, backslashes, symlinks (a dangling one and a symlinked Actor folder included), the case mismatch reason, the severity of each readme and changelog reason, a backslash in an absolute or escaping readme, the default Dockerfile lookup in every context (an ignored default included), and the message for each severity.
  • test/local/commands/push-actor-paths.test.ts: errors stop push with exit code 5 before the login check and any Actor read or write; a default Dockerfile outside ../docker and the built-in Dockerfile in ../docker stop it; no Dockerfile with dockerContextDir .. or without a context pushes; a default .actor/Dockerfile in the context pushes; the js-cypress shape and a missing changelog print a warning and push, with stdout clean under --json; an absolute readme, a readme outside the upload root, a missing dockerfile, and a missing schema stop it.
  • test/local/commands/push-monorepo.test.ts: the context within the Actor folder now has a Dockerfile of its own.
  • Lint, format, build, update-docs (no change), and test:local pass, except the two [python] fixture tests that need a newer Python.

Notes for reviewers

  • Default Dockerfile: before this change, push let through the two cases from the review. With "dockerContextDir": "../docker" and a Dockerfile in the Actor folder, the build failed on open ../Dockerfile; without it, the platform built docker/ with its built-in Dockerfile and ignored docker/Dockerfile. Both now stop push and name the Dockerfile the platform would use.
  • Warning severity: the worker logs a warning for a readme or changelog it cannot find and falls back to its default lookup (.actor/ACTOR.md, .actor/README.md, then README.md or README in the Actor folder, and likewise for the changelog), so the build succeeds. Failing the push there would block Actors that build fine today. A missing dockerfile falls back as well, but to a Dockerfile the user did not choose, so it stays an error.
  • The js-cypress template sets "readme": "./README.md" with README.md in the Actor folder and none in .actor/. It now pushes with a warning instead of failing; fixing the template is a separate follow-up in apify/actor-templates.
  • Reproduced through the built CLI with no login: both review scenarios exit with code 5 before the login check, and the js-cypress shape prints the warning and then stops only at the login check with exit code 1.
  • The helper returns a list instead of throwing, so stage 2 of monorepo push can pass the repository root as uploadRoot.
  • Roots are real paths, as the resolver returns them, so a symlinked --dir is not mistaken for an escape.

🤖 Generated with Claude Code

push reads through a symlink and uploads the content at the link's own
path, so a link to a file outside the upload root builds on the platform.
The realpath escape rule refused it; the uploaded files rule already
rejects anything push does not send.
The worker copies inputSchema over input and outputSchema over output
before resolving, so a schema path under the alias was never checked.
The worker treats an empty string as an unset field, so an empty value
was wrongly reported as the .actor folder being a directory. A backslash
passes path.resolve on Windows but is a filename character on the Linux
worker, so it is refused before any resolution.
The lib test now builds its fixture through useTempPath, as the other
lib tests do, adds a changelog file so the case-insensitive match is
checked for all three listed fields, and covers the reason given when
the uploaded file differs only in case. The command test restores the
real process.exitCode next to the mocked copy.
checkActorPaths resolves the roots and builds the posix set of uploaded
files once and hands them to every field, instead of each field
recomputing them.
@DaveHanns
DaveHanns marked this pull request as ready for review October 26, 2026 15:46
@DaveHanns
DaveHanns requested a review from l2ysho as a code owner October 26, 2026 15:46

@apify-factory apify-factory left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good, nothing blocking.

🏭 Factory review — ℹ️ not auto-approved

This change was reviewed but cannot be auto-approved: a gate failed, so the review cannot end in an approval.

check result
Static gates ❌ 14/16 — base-branch, max-lines
Review findings ✅ none
Reviewer escalation ❌ The new check makes apify push fail on paths that used to push fine, based on platform worker rules this repo can't confirm (for example, a missing readme or dockerfile used to fall back to the default).
Juror 1 — claude-sonnet-5-5, adversarial ⏭️ skipped — a gate failed
Why it was not auto-approved

Failed gates:

  • base-branch: base is feat/push-monorepo-preflight, required master
  • max-lines: 646 changed lines (+644/-2), limit 200

The comments above are the review itself; this section only explains the approval decision. Push a change and request @apify-factory again to re-run it, or ask a human to review.

Reviewed e3edc7960 · workflow run

@apify-factory apify-factory left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good, nothing blocking.

@DaveHanns DaveHanns linked an issue Oct 27, 2026 that may be closed by this pull request
3 tasks
Base automatically changed from feat/push-monorepo-preflight to master October 27, 2026 15:04
…actor-json-paths

# Conflicts:
#	src/commands/actors/push.ts
#	src/lib/docker-context.ts
@apify-service-account apify-service-account added the tested Temporary label used only programatically for some analytics. label Oct 27, 2026
@apify-service-account apify-service-account added the t-builders Issues owned by the Builders team. label Oct 27, 2026
Comment thread src/lib/actor-paths.ts
Comment thread src/commands/actors/push.ts Outdated

@l2ysho l2ysho left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In followup we should check apify/actor-templates -> at least js-cypress template will break due to its invalid README path actor.json

@l2ysho

l2ysho commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

one preexisting problem I found -> run and push does not use the same input schema

with inputSchema: "./custom_input.json":

  • push and the platform use custom_input.json.
  • apify run, validate-schema, edit-input-schema and generate-schema-types fall through to .actor/INPUT_SCHEMA.json

we should resolve also this in followup issue.

… does

An absolute or escaping readme or changelog fails the build even with a backslash in it, so it stays an error. A default Dockerfile that push does not upload is skipped, as the platform never sees it.
A broken Actor now fails with the same error and exit code with or without a login.
@DaveHanns
DaveHanns requested a review from l2ysho October 30, 2026 08:35
@DaveHanns

Copy link
Copy Markdown
Contributor Author

Thanks for checking the templates.

Ready for another look.

@l2ysho l2ysho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nice 🚀

This branch has not been deployed

No deployments
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. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validate actor.json paths before push

4 participants