Skip to content

apify push can hang indefinitely and can exit 0 before the latest tag is applied #1131

@l2ysho

Description

@l2ysho

apify push has two long-standing reliability holes that are surfacing in downstream E2E suites (e.g. crawlee-python). Both stem from the same root
cause: the command trusts the build log stream as its source of truth and never independently polls the build or the actor's tags.

1. Hangs indefinitely when the log stream doesn't end

src/commands/actors/push.ts:193-195:

const waitForFinishMillis = Number.isNaN(this.flags.waitForFinish)
    ? undefined
    : Number.parseInt(this.flags.waitForFinish!, 10) * 1000;

Number.isNaN does not coerce, so this branch is always taken — when --wait-for-finish is omitted, waitForFinishMillis is NaN. In
outputJobLog (src/lib/utils.ts:610) the if (timeoutMillis) check is then falsy and no timeout is installed, so the command waits on
stream.once('end') forever. A flaky platform log stream that never emits end deadlocks the process; CI only escapes via its own outer timeout.

2. Exits 0 before the build is actually latest

After outputJobLog resolves, push does a single apifyClient.build(build.id).get() and prints status. If the stream ended early and the build is
still RUNNING/READY, push warns and exits 0. Even on SUCCEEDED, push never verifies that actor.taggedBuilds[buildTag].buildId === build.id.
Callers that immediately actor.start() against the latest tag race the platform's tag-update step and either hit the previous build or fail.

For comparison, src/lib/commands/run-on-cloud.ts polls runs to a terminal status after the log stream ends — push has never done the equivalent.

Proposed fix

  1. Fix the NaN check so an absent flag means "no log-stream timeout, but still poll the build":

    const parsed = this.flags.waitForFinish ? Number.parseInt(this.flags.waitForFinish, 10) : NaN;
    const waitForFinishMillis = Number.isFinite(parsed) ? parsed * 1000 : undefined;
  2. After outputJobLog returns, poll apifyClient.build(id).get() until terminal status, with a sensible cap when --wait-for-finish isn't set.

  3. When a buildTag is in play, after SUCCEEDED poll apifyClient.actor(actorId).get() until taggedBuilds[buildTag].buildId === build.id (short
    timeout, e.g. 30 s).

  4. Return a non-zero exit code for non-terminal end states (RUNNING/READY) — "still running" should not be exit 0.

History

These behaviors have been present since the log-streaming feature was added in 2018 (f80bc65).

Metadata

Metadata

Assignees

Labels

t-dxIssues owned by the DX team.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions