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
-
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;
-
After outputJobLog returns, poll apifyClient.build(id).get() until terminal status, with a sensible cap when --wait-for-finish isn't set.
-
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).
-
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).
apify pushhas two long-standing reliability holes that are surfacing in downstream E2E suites (e.g.crawlee-python). Both stem from the same rootcause: 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:Number.isNaNdoes not coerce, so this branch is always taken — when--wait-for-finishis omitted,waitForFinishMillisisNaN. InoutputJobLog(src/lib/utils.ts:610) theif (timeoutMillis)check is then falsy and no timeout is installed, so the command waits onstream.once('end')forever. A flaky platform log stream that never emitsenddeadlocks the process; CI only escapes via its own outer timeout.2. Exits 0 before the build is actually
latestAfter
outputJobLogresolves, push does a singleapifyClient.build(build.id).get()and prints status. If the stream ended early and the build isstill
RUNNING/READY, push warns and exits 0. Even onSUCCEEDED, push never verifies thatactor.taggedBuilds[buildTag].buildId === build.id.Callers that immediately
actor.start()against thelatesttag race the platform's tag-update step and either hit the previous build or fail.For comparison,
src/lib/commands/run-on-cloud.tspolls runs to a terminal status after the log stream ends — push has never done the equivalent.Proposed fix
Fix the NaN check so an absent flag means "no log-stream timeout, but still poll the build":
After
outputJobLogreturns, pollapifyClient.build(id).get()until terminal status, with a sensible cap when--wait-for-finishisn't set.When a
buildTagis in play, afterSUCCEEDEDpollapifyClient.actor(actorId).get()untiltaggedBuilds[buildTag].buildId === build.id(shorttimeout, e.g. 30 s).
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).