Skip to content

feat(cli): add wait commands and enhance agentic output for builds and runs - #1168

Merged
l2ysho merged 14 commits into
masterfrom
1135-improve-apify-cli-buildrun-output-for-agentic-workflows
Jun 25, 2026
Merged

feat(cli): add wait commands and enhance agentic output for builds and runs#1168
l2ysho merged 14 commits into
masterfrom
1135-improve-apify-cli-buildrun-output-for-agentic-workflows

Conversation

@l2ysho

@l2ysho l2ysho commented May 29, 2026

Copy link
Copy Markdown
Contributor

Introduces dedicated wait commands and unifies output for several build/run commands for better CI/CD integration.

closes #1140

…d runs

Introduces dedicated `wait` commands and unifies output for several build/run commands for better CI/CD integration.
@l2ysho l2ysho self-assigned this May 29, 2026
@l2ysho l2ysho added the t-builders Issues owned by the Builders team. label May 29, 2026
@l2ysho l2ysho linked an issue May 29, 2026 that may be closed by this pull request
@github-actions github-actions Bot added this to the 141st sprint - Tooling team milestone May 29, 2026
@l2ysho

l2ysho commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

lets wait for #1134

The earlier change made `apify push` exit non-zero whenever the build had
not reached SUCCEEDED, including non-terminal RUNNING/READY states. That
broke the E2E lifecycle tests and any CI that treats a successful upload
+ in-progress build as a successful push. Only treat a terminal,
non-SUCCEEDED status as a push failure.
l2ysho and others added 3 commits June 10, 2026 16:07
waitForTerminalStatus overwrote a still-RUNNING job's status with a
synthetic TIMED-OUT when the client-side --timeout elapsed, so an agent
consuming the JSON could not tell "I gave up waiting" from "the platform
job timed out" (both reported TIMED-OUT, exit 2).

It now returns { job, timedOutWaiting } with the real platform status,
and the wait commands map a give-up to a distinct WaitTimedOut (6) exit
code, a timedOutWaiting JSON field, and give-up-specific messaging. The
exit-code mapping lives in a shared exitCodeForWaitResult helper.

Also drop the unused consoleActorUrl/consoleKeyValueStoreUrl exports.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…output-for-agentic-workflows' into 1135-improve-apify-cli-buildrun-output-for-agentic-workflows
@l2ysho
l2ysho marked this pull request as ready for review June 15, 2026 12:15
Comment thread src/commands/builds/wait.ts Outdated
Comment thread src/commands/builds/wait.ts Outdated
Comment thread docs/reference.md Outdated
l2ysho and others added 8 commits June 22, 2026 13:16
Co-authored-by: Edyta <142720610+szaganek@users.noreply.github.com>
Co-authored-by: Edyta <142720610+szaganek@users.noreply.github.com>
…output-for-agentic-workflows' into 1135-improve-apify-cli-buildrun-output-for-agentic-workflows
Comment thread src/lib/consts.ts
@l2ysho
l2ysho merged commit d3c3e5b into master Jun 25, 2026
24 checks passed
@l2ysho
l2ysho deleted the 1135-improve-apify-cli-buildrun-output-for-agentic-workflows branch June 25, 2026 18:40

@DaveHanns DaveHanns 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.

Overall great job 🚀
Few nits and suggestions tho.

Comment thread src/commands/actors/start.ts
Comment thread src/commands/builds/create.ts
timedOutWaiting: boolean;
}

export async function waitForTerminalStatus(options: WaitForJobOptions): Promise<WaitForJobResult> {

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.

Nit: this would deserve unit test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 5f7a180

return !!status && TERMINAL_STATUSES.has(status);
}

export function exitCodeForJobStatus(status: string | undefined, kind: 'build' | 'run'): number {

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.

Suggestion: instead of hardcoding status string literals ('TIMED-OUT', 'ABORTING', …), the ACTOR_JOB_STATUSES should be used, e.g.: ACTOR_JOB_STATUSES.TIMED_OUT, ...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 3124ca0


export interface ResultSummaryOptions {
resultLabel: string; // e.g. "Apify push result"
overallStatus: 'SUCCEEDED' | 'FAILED' | 'ABORTED' | 'TIMED-OUT' | 'RUNNING';

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.

Suggestion: These should not be hardcoded, but derived from @apify/consts.

Besides that having the overallStatus as subset of all possible job statuses is not really much effective here as all its callers cast the job.status as never to fit even if they do not fit. For instance, overallStatus: run.status as never, in runs/wait.ts where

(property) ActorRunListItem.status: "READY" | "RUNNING" | "SUCCEEDED" | "FAILED" | "ABORTING" | "ABORTED" | "TIMING-OUT" | "TIMED-OUT"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 0dbdf53


const TERMINAL_STATUSES = new Set<string>(ACTOR_JOB_TERMINAL_STATUSES as readonly string[]);

export function isTerminalStatus(status: string | undefined): status is TerminalStatus {

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.

Nit: this feels like over-optimization. For a 4-element array, the dedicated TERMINAL_STATUSES Set (and the as readonly string[] cast) isn't worth it. ACTOR_JOB_TERMINAL_STATUSES.includes(...) should be good enough.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in f21d0ab

return !!status && TERMINAL_STATUSES.has(status);
}

export function exitCodeForJobStatus(status: string | undefined, kind: 'build' | 'run'): number {

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.

Here and through the rest of the file, status: string and kind: 'build' | 'run' are stringly-typed. Suggest deriving two named types and using them throughout instead:

type JobStatus = (typeof ACTOR_JOB_STATUSES)[keyof typeof ACTOR_JOB_STATUSES];
type JobType = (typeof ACTOR_JOB_TYPES)[keyof typeof ACTOR_JOB_TYPES];

then use JobStatus for the status parameters and JobType for kind across exitCodeForJobStatus, isTerminalStatus, etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in a71de07

}
}

export async function fetchLogTail(apifyClient: ApifyClient, jobId: string, maxLines = 20): Promise<string[]> {

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.

Suggestion: fetchLogTail is generally useful and overlaps with the output improvements in the sibling agentic-output PRs (e.g. #1198). Once these land, this should be the single shared implementation they all import, rather than each PR re-implementing it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 28f73f0

Comment on lines +466 to +472
const refreshedBuild = await apifyClient.build(build.id).get();
if (!refreshedBuild) {
error({ message: `Could not fetch build with ID "${build.id}" after deployment.` });
process.exitCode = CommandExitCodes.BuildFailed;
return;
}
build = refreshedBuild;

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.

Nit: the intermediate refreshedBuild isn't needed, since the !refreshedBuild branch returns, you can null-check and assign to build directly without the extra variable.

Suggested change
const refreshedBuild = await apifyClient.build(build.id).get();
if (!refreshedBuild) {
error({ message: `Could not fetch build with ID "${build.id}" after deployment.` });
process.exitCode = CommandExitCodes.BuildFailed;
return;
}
build = refreshedBuild;
build = await apifyClient.build(build.id).get();
if (!build) {
error({ message: `Could not fetch build with ID "${build.id}" after deployment.` });
process.exitCode = CommandExitCodes.BuildFailed;
return;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 2e31e4d

Comment on lines +174 to +180
const refreshed = await client.build(build.id).get();
if (!refreshed) {
error({ message: `Could not refresh build status for build "${build.id}".` });
process.exitCode = 1;
return;
}
build = refreshed;

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.

Nit: same as the refreshedBuild case in push.ts, the intermediate refreshed isn't needed; since the !refreshed branch returns, null-check and assign to build directly.

Suggested change
const refreshed = await client.build(build.id).get();
if (!refreshed) {
error({ message: `Could not refresh build status for build "${build.id}".` });
process.exitCode = 1;
return;
}
build = refreshed;
build = await client.build(build.id).get();
if (!build) {
error({ message: `Could not refresh build status for build "${build.id}".` });
process.exitCode = 1;
return;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The refreshed intermediate no longer exists on master — this block was replaced by waitForTerminalStatus in a later revision of the PR.

Comment on lines +64 to +65
? ((await apifyClient.build(jobId).get()) as Build | undefined)
: ((await apifyClient.run(jobId).get()) as ActorRun | undefined);

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.

Suggestion: the casting on both of these lines is unnecessary. The get already returns correct type.

In general, I have noticed that Claude loves to solve every tiny TS issue with casting. We could probably fight that with stricter linting 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 3664c00

patrikbraborec pushed a commit that referenced this pull request Jun 26, 2026
…d runs (#1168)

Introduces dedicated `wait` commands and unifies output for several
build/run commands for better CI/CD integration.

closes #1140

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Edyta <142720610+szaganek@users.noreply.github.com>
l2ysho added a commit that referenced this pull request Jul 1, 2026
The review asked that --silent gate the --json branch in call. That is
already handled by finalizeRun (silent returns before json), but the
existing silent test used json: false. Add a case with both set to lock
that silent wins over json and nothing is printed.

Ref: #1168 (comment)
l2ysho added a commit that referenced this pull request Jul 1, 2026
These are missing findings from #1168. Releasing as chore to skip release notes and changelog.
l2ysho added a commit that referenced this pull request Jul 8, 2026
fetchRunLogTail keeps its succeeded-run guard and 10-line tail but
delegates the fetch/split/slice to the shared fetchLogTail, which now
also trims trailing whitespace.

Resolves #1168 (comment)
l2ysho added a commit that referenced this pull request Jul 8, 2026
Add consoleActorUrl and consoleKeyValueStoreUrl, use the builders in
start/call/run-result/runs info/builds info instead of inlined URLs,
and standardize build URLs on the /builds/ path format.

Resolves #1168 (comment)
l2ysho added a commit that referenced this pull request Jul 8, 2026
Add a generic overload mapping kind to Build/ActorRun so callers no
longer cast the returned job.

Resolves #1168 (comment)
Resolves #1168 (comment)
Resolves #1168 (comment)
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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add wait commands for Actor builds and runs

5 participants