Add local dev folder to POCv2 - #19
Merged
Pijukatel merged 11 commits intoAug 20, 2026
Merged
Conversation
Register an Actor's host source folder and mount it over the image working directory on every run, so a local `tsc` is enough to pick up TS changes. The folder is registered through a new local-only endpoint outside the emulated Apify /v2 surface, and through a form on the console Actor page. Registration verifies the folder exists host-side via a create-only probe container, since the runtime is itself containerized and cannot stat a host path. Mounts (not Binds) is used throughout so a missing source is an error rather than an auto-created empty directory masking the working directory. Requirements updated to match: dockerode instead of a shelled-out docker inspect, --mount semantics instead of -v, the new endpoint namespace, and the console no longer being strictly view-only. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
Code comments cited files that are gitignored, so they were dead pointers for anyone reading the repository. They now cite moby/dockerode behaviour in its own terms or the committed requirements sections instead. The reasoning is unchanged; only the references are. Probe image resolution moves onto a shared resolveTaggedBuild helper, also now used by the run-start path it was duplicating. This drops an arbitrary-tag fallback: an Actor whose only build is not tagged latest is refused at registration, matching what a tag-less run already does rather than succeeding against a build no run could reach. Adds coverage for the build-time working-directory persist, both when the build reports one and when it does not and a stored value must survive. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
A tag whose build record was deleted reported "Actor has no build tagged X", which was wrong: the tag exists. Build resolution now reports the two cases apart, both still 404 record-not-found, and both are covered by tests. The default build tag becomes one exported constant that the run route and the run service import, replacing three hand-synced 'latest' literals. Adds a services-layer test that a run's mount is derived from the Actor's stored fields, covering both set and cleared. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
…edAt Registering a dev folder bumped the Actor's modifiedAt, which is exposed on /v2 -- the emulated API could observe a purely local registration. Dev-folder writes no longer touch it, and clearing an unregistered Actor writes nothing. The dev-folder pipeline moves out of the actors service into its own module, which lets it reuse the builds service instead of duplicating a registry read to avoid a cycle. HTTP statuses and error types move to the route layer. A tag whose build was deleted reports the same bare not-found the run route gave before this branch, so run-start behaviour is unchanged throughout. Probe containers are labelled and swept by orphan reconciliation, the driver's probe method is required rather than optional, whitespace-only paths are rejected instead of silently clearing, console styles move into the shared template, and the e2e test copies the sample Actor instead of editing it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
Sweeping probe containers alongside run containers put both label keys in one listContainers filter, but the daemon requires a container to carry every key given in a single call, so the query matched nothing and reconciliation stopped reclaiming orphaned run containers -- and their anonymous volumes with them. Each label key now gets its own query and the results are unioned by id. The stub in the unit test now models that daemon-side behaviour, so a combined call returns nothing and fails, and a further test pins one value per call. The dev-folder route module regains the mount(router, deps) shape its siblings use, with the server owning the /actor-runtime prefix as it owns /v2. Also corrects the rejection message for an Actor whose build is not tagged latest, and the requirements and comments that repeated its framing. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
The test installed dependencies into the folder it then bind-mounted, so the Actor resolved its imports from the host copy and the run would have passed whether or not the image's node_modules survived the mount. The copied folder now has node_modules removed before registration, so resolution can only come through the anonymous volume. Types the dockerode stubs in the driver unit test, which had drifted to casting empty mock-call tuples; the build does not cover test files, so this went unnoticed. Documents the probe label and its startup sweep, records that host-level volume and container invariants may be read from the Docker CLI since no Apify command observes them, and corrects the claim that the runtime is always containerized. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
startRuntimeContainer binds fixed host ports, and vitest runs test files in parallel by default, so the two e2e files raced for 3333/3000 and whichever lost died on "port is already allocated". test:e2e now disables file parallelism, and the port-binding site says why so a third file does not rediscover it. The unit and integration suites keep running in parallel. Also corrects the driver doc that still claimed one bind-mount site, drops a cast the stub's own parameter type makes unnecessary, states the registration precondition in the README as the latest tag rather than any build, and rewraps a CLAUDE.md bullet that split an inline code span. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
Docker accepts a regular file as a bind source, so a file path passed the dev-folder check and only failed later as a broken run. The probe now binds <path>/. , which the daemon rejects for a file while still reporting a missing path as missing, and the route answers with a message naming the real cause. The image working directory moves from the Actor to the build that produced it, and a run mounts against the working directory of the build it resolved. An Actor with several tags could otherwise mount at a path taken from a different tag's image while the console reported the mount as applied. The endpoint is also served under /v2 so it can be reached without a ../ prefix, since apify api builds its URLs onto a /v2 base. Both mounts share one handler and one auth pass; the documented form drops the dots. Adds an end-to-end case for the workflow this exists for: two runs of the same build, recompiled in between, with no push and no rebuild. Formats CLAUDE.md so format:check passes, and forces the end-to-end re-pushes, which apify push refuses once a build has bumped the Actor past the pushed files' timestamps. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
The bind-mount sections described how today's code works -- library calls, Docker API field names, the daemon's own error strings, internal file names -- rather than what is required. They now state outcomes: the path must be absolute, must exist on the host, and must be a directory; being unable to verify is never reported as missing; the mount applies only when the folder and the resolved build's working directory are both known; a folder that has gone away fails the run visibly; the image's dependencies survive the mount. The constraint that the runtime cannot judge a host path from its own filesystem stays, without prescribing the workaround. Restores the note that the console is no longer strictly view-only, which the previous trim dropped while the console does write. Comments and test titles quoting the old wording are repointed. Formats api.md so format:check passes. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
Registration needed an image only to create the throwaway container that asks the daemon whether the submitted path is a mountable directory. Borrowing the Actor's latest build for that made registration depend on a prior build and turned a folder problem into a message about build tags, while the image itself contributes nothing to the check. The driver now builds and owns a minimal image for that purpose, so a folder can be registered against an Actor that has never been built. The reported status is now just the registered folder. Whether a mount applies depends on which build a run resolves, so an Actor-level field could not answer it honestly. The tag-less run default is untouched -- that belongs to the run route, not to this feature. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
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.
This PR adds an option to run code changes as quickly as possible.
It allows the user to bind-mount the local development folder into the already build docker container and run the code updated from the local dev folder.
Compiled/non compiled difference
For Python or JS actors
For TS actors
Workflow change
The old Apify workflow:
The Actor runtime workflow:
apify push (rebuilds the image)
apify call (run the actor)
MAKE CODE CHANGE
set up dev folder (through cli+API or frontend form)
(No more building !!!!!)
apify call (run the actor)
MAKE CODE CHANGE
apify call (run the actor)
Limitations