Skip to content

apify push: concurrent pushes silently overwrite each other — no conflict detection (TOCTOU race) #1436

Description

@kuntal1461

Bug description

When two apify push operations run against the same actor concurrently (e.g. two CI/CD pipeline runs, two team members, or two terminals), the last write wins with no error and no warning. The actor version source is silently overwritten, and both operations queue independent builds — potentially deploying code that neither developer intended as the final state.

Steps to reproduce

  1. Have an actor already pushed to the platform.
  2. From two terminals (or two CI jobs) simultaneously run apify push with the same credentials and the same actor.json name.
  3. Observe: both commands exit 0, both report success.

Expected behavior

The second push should detect the conflict and fail with a clear message, e.g.: "Actor was modified by another process since you started this push. Re-pull and retry, or use --force to override."

Actual behavior

Both pushes succeed silently. The version source is overwritten by the last PUT /actor/{id}/versions/{v} call. Both builds are queued; the one that finishes last and claims the latest tag wins. The other build ran against overwritten source code.

Root cause

The staleness check in src/commands/actors/push.ts (lines 369–393) is:

  1. Client-side only — it compares local file mtime against actor.modifiedAt read at the start of the command. There is no server-side conditional update (no ETag / If-Match / If-Unmodified-Since).

  2. Raceable (TOCTOU) — the window between GET actor (read modifiedAt) and PUT version (write source) is unbounded. A concurrent push that lands in that window is undetected.

  3. Skipped for large files — the check is guarded by filesSize < MAX_MULTIFILE_BYTES. Actors larger than ~5 MB (ZIP upload path) have zero conflict detection.

  4. Bypassed by --force — common in CI pipelines, which removes the only soft guard.

Impact

  • CI/CD pipelines: two pipeline runs triggered in parallel (e.g. two PRs merged in quick succession) will race. Wrong source code deploys silently — no alert, no failed step.
  • Teams: two developers pushing the same actor at the same time silently lose one person's changes.
  • Severity is amplified because the failure mode is silent — the command exits 0 and reports success.

Proposed fix

Short-term (CLI-only, partial): Remove the filesSize < MAX_MULTIFILE_BYTES guard so the staleness check also runs for ZIP uploads. Add a warning on the ZIP path noting it cannot fully prevent races.

Proper fix (requires API change): Add conditional update support to the platform API — PUT /actor/{id}/versions/{v} should accept an If-Unmodified-Since or ETag header. The CLI passes the modifiedAt value it read at step 1; the server rejects with HTTP 412 if the actor was modified since. The CLI surfaces a clear error to the user.

Environment

  • Reproducible on any platform version (race is architectural, not version-specific)
  • Affects both apify push and actors push (they share the same ActorsPushCommand)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    t-buildersIssues owned by the Builders team.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions