Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions src/driver-opts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,14 @@ describe("driver-opts parsing", () => {

await startBuildkitd(4, "tcp://127.0.0.1:1234", undefined, []);

// Verify that execa was called without environment variables
// Verify that execa was called with only the default environment variables
expect(mockExeca).toHaveBeenCalledTimes(1);
const commandCall = mockExeca.mock.calls[0][0] as string;

// Should not contain any environment variables, no env command
expect(commandCall).not.toContain("OTEL_");
expect(commandCall).not.toContain("sudo env");
expect(commandCall).toContain(
"BUILDKIT_SESSION_HEALTHCHECK_MAX_FAILURES='10'",
);
expect(commandCall).toContain("nohup sudo");
});

Expand All @@ -140,13 +141,14 @@ describe("driver-opts parsing", () => {

await startBuildkitd(4, "tcp://127.0.0.1:1234", undefined, undefined);

// Verify that execa was called without environment variables
// Verify that execa was called with only the default environment variables
expect(mockExeca).toHaveBeenCalledTimes(1);
const commandCall = mockExeca.mock.calls[0][0] as string;

// Should not contain any environment variables, no env command
expect(commandCall).not.toContain("OTEL_");
expect(commandCall).not.toContain("sudo env");
expect(commandCall).toContain(
"BUILDKIT_SESSION_HEALTHCHECK_MAX_FAILURES='10'",
);
expect(commandCall).toContain("nohup sudo");
});

Expand Down
5 changes: 5 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
pruneBuildkitCache,
logBuildCacheContents,
logBuildkitdLogTail,
warnOnDroppedBuildkitSessions,
logDatabaseHashes,
writeDockerContainerBuildkitdTomlFile,
} from "./setup_builder";
Expand Down Expand Up @@ -771,6 +772,10 @@ void actionsToolkit.run(
let integrityCheckPassed: boolean | null = null;

try {
// Surface dropped buildx sessions (e.g. from memory pressure) so
// 'no active session' push failures are explained in the job log.
await warnOnDroppedBuildkitSessions();

// Step 1: Shut down buildkitd if this instance started it.
// When setup is called multiple times in one job, only the first
// instance starts buildkitd; subsequent instances reuse it and
Expand Down
40 changes: 38 additions & 2 deletions src/setup_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,13 @@ export async function startBuildkitd(
const dnsNameservers = await getRoutableHostDns();
await writeBuildkitdTomlFile(parallelism, addr, dnsNameservers);

// Parse driver-opts to extract environment variables
const envVars: Record<string, string> = {};
// Parse driver-opts to extract environment variables.
// Defaults keep the credential-carrying buildx session alive through
// transient client stalls (e.g. severe memory pressure during a build);
// user-supplied env.* driver-opts take precedence.
const envVars: Record<string, string> = {
BUILDKIT_SESSION_HEALTHCHECK_MAX_FAILURES: "10",
};
if (driverOpts && driverOpts.length > 0) {
core.info(`Processing ${driverOpts.length} driver-opt(s)`);
for (const opt of driverOpts) {
Expand Down Expand Up @@ -428,6 +433,37 @@ export async function logBuildkitdLogTail(): Promise<void> {
}
}

/**
* Scans the buildkitd log for dropped-session signatures and surfaces a clear
* warning. When the buildx session dies mid-build (typically because severe
* memory pressure stalls the client past the healthcheck tolerance), the
* eventual failure is a confusing registry auth error at push time
* ("no active session for <id>: context deadline exceeded"), so explain the
* real cause here.
*/
export async function warnOnDroppedBuildkitSessions(): Promise<void> {
try {
const { stdout } = await execAsync(
"grep -E 'healthcheck failed fatally|no active session' /tmp/buildkitd.log 2>/dev/null | tail -n 10 || true",
);
if (!stdout.trim()) {
return;
}
core.warning(
"BuildKit dropped one or more buildx sessions during this job. " +
"This usually means the runner was under severe memory pressure and the build client " +
"stalled past the session healthcheck tolerance. If a push failed with " +
"'no active session for <id>' or an auth error at /src/session/auth/auth.go, this is the " +
"root cause — consider a runner with more memory. Matching buildkitd log lines:\n" +
stdout.trim(),
);
} catch (error) {
core.debug(
`Could not scan buildkitd log for dropped sessions: ${(error as Error).message}`,
);
}
}

export async function startAndConfigureBuildkitd(
parallelism: number,
buildkitdPath?: string,
Expand Down
Loading