Skip to content

Commit 4900183

Browse files
depollCopilot
andcommitted
Fix runtime provisioning regressions
Avoid false amd64 mismatch failures by relying on Docker platform pinning at container create time, skip reconciler orphan cleanup for provisioning runners, and tolerate missing archive cleanup during concurrent runner lifecycle events. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0050936 commit 4900183

4 files changed

Lines changed: 30 additions & 45 deletions

File tree

backend/src/services/dockerRunner.ts

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -89,33 +89,6 @@ export async function pullRunnerImage(
8989
): Promise<string> {
9090
const d = initDocker();
9191
const platform = `linux/${architecture}`;
92-
93-
const splitImageRef = (ref: string): { repo: string; tag: string } => {
94-
const lastColon = ref.lastIndexOf(':');
95-
const lastSlash = ref.lastIndexOf('/');
96-
if (lastColon > lastSlash) {
97-
return { repo: ref.slice(0, lastColon), tag: ref.slice(lastColon + 1) };
98-
}
99-
return { repo: ref, tag: 'latest' };
100-
};
101-
102-
const { repo, tag: originalTag } = splitImageRef(RUNNER_IMAGE);
103-
const archTag = `${originalTag}-${architecture}`;
104-
const platformTag = `${repo}:${archTag}`;
105-
106-
// Check if we already have the platform-specific tag with correct architecture
107-
try {
108-
const existingImage = await d.getImage(platformTag).inspect();
109-
const imageArch = normalizeImageArchitecture(existingImage.Architecture);
110-
111-
if (imageArch === architecture) {
112-
console.log(`Image ${platformTag} already exists with correct architecture (${existingImage.Architecture})`);
113-
return platformTag;
114-
}
115-
console.log(`Image ${platformTag} exists but has wrong architecture (${existingImage.Architecture}), re-pulling...`);
116-
} catch {
117-
// Image doesn't exist, need to pull
118-
}
11992

12093
console.log(`Pulling image ${RUNNER_IMAGE} for ${platform}...`);
12194

@@ -140,23 +113,8 @@ export async function pullRunnerImage(
140113
});
141114
});
142115
});
143-
144-
// Verify the pulled image has the correct architecture
145-
const pulledImage = await d.getImage(RUNNER_IMAGE).inspect();
146-
console.log(`Pulled image architecture: ${pulledImage.Architecture}`);
147-
assertImageArchitecture(RUNNER_IMAGE, pulledImage.Architecture, architecture);
148-
149-
// Tag the pulled image with architecture-specific tag
150-
console.log(`Tagging image as ${platformTag}...`);
151-
const image = d.getImage(RUNNER_IMAGE);
152-
await image.tag({ repo, tag: archTag });
153-
154-
// Verify the tagged image
155-
const taggedImage = await d.getImage(platformTag).inspect();
156-
console.log(`Tagged image ${platformTag} architecture: ${taggedImage.Architecture}`);
157-
assertImageArchitecture(platformTag, taggedImage.Architecture, architecture);
158-
159-
return platformTag;
116+
117+
return RUNNER_IMAGE;
160118
}
161119

162120
/**

backend/src/services/reconciler.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ const getAllEnabledPools = db.prepare('SELECT * FROM runner_pools WHERE enabled
5555
const deleteRunner = db.prepare('DELETE FROM runners WHERE id = ?');
5656
const updateRunnerStatus = db.prepare('UPDATE runners SET status = ?, updated_at = datetime(\'now\') WHERE id = ?');
5757

58+
export function shouldSkipGitHubExistenceCheck(status: string): boolean {
59+
return status === 'pending' || status === 'configuring';
60+
}
61+
5862
/**
5963
* Start the periodic reconciliation service
6064
*/
@@ -157,6 +161,11 @@ async function reconcileRunnersInternal(): Promise<void> {
157161
for (const runner of localRunners) {
158162
stats.checked++;
159163

164+
// Newly provisioning runners are expected to not exist in GitHub yet.
165+
if (shouldSkipGitHubExistenceCheck(runner.status)) {
166+
continue;
167+
}
168+
160169
// Check if runner exists in GitHub (by ID or name)
161170
const existsInGitHub =
162171
(runner.github_runner_id && ghRunnerIds.has(runner.github_runner_id)) ||

backend/src/services/runnerManager.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,12 @@ export async function downloadRunner(
441441
}
442442

443443
// Cleanup archive
444-
await fs.unlink(archivePath);
444+
await fs.unlink(archivePath).catch((error: unknown) => {
445+
const code = (error as NodeJS.ErrnoException).code;
446+
if (code !== 'ENOENT') {
447+
throw error;
448+
}
449+
});
445450

446451
// Update runner directory in database
447452
updateRunnerDir.run(runnerDir, runnerId);

backend/tests/reconciler.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import fs from 'fs/promises';
77
import path from 'path';
88
import os from 'os';
99
import { db } from '../src/db/index.js';
10+
import { shouldSkipGitHubExistenceCheck } from '../src/services/reconciler.js';
1011

1112
// Test directory for runner cleanup tests
1213
const TEST_RUNNERS_DIR = path.join(os.tmpdir(), 'action-packer-test-runners');
@@ -16,6 +17,18 @@ let cleanupOrphanedDirectories: () => Promise<number>;
1617
let withTimeout: <T>(promise: Promise<T>, ms: number, operation: string) => Promise<T>;
1718

1819
describe('Reconciler', () => {
20+
describe('shouldSkipGitHubExistenceCheck', () => {
21+
it('skips pending and configuring runners', () => {
22+
expect(shouldSkipGitHubExistenceCheck('pending')).toBe(true);
23+
expect(shouldSkipGitHubExistenceCheck('configuring')).toBe(true);
24+
});
25+
26+
it('does not skip online/offline runners', () => {
27+
expect(shouldSkipGitHubExistenceCheck('online')).toBe(false);
28+
expect(shouldSkipGitHubExistenceCheck('offline')).toBe(false);
29+
});
30+
});
31+
1932
describe('withTimeout', () => {
2033
beforeEach(async () => {
2134
// Dynamically import to get fresh module

0 commit comments

Comments
 (0)