Skip to content

Commit 7c9fc68

Browse files
committed
fix(cli): wait for model actions before dispatching conformance tests
genkit dev:test-model dispatched test requests immediately after the runtime appeared, without checking whether the target model actions were actually registered. This caused 404 errors on /api/runAction for models that hadn't finished registering yet (especially with non-JS runtimes that may have slower startup). Add waitForActions() that polls /api/actions after the runtime is detected and waits until all model keys referenced in the test suites are present before starting tests. Polls every 500ms for up to 30 seconds, with periodic progress logging. Fixes #4599
1 parent 952ee90 commit 7c9fc68

File tree

1 file changed

+54
-0
lines changed

1 file changed

+54
-0
lines changed

genkit-tools/cli/src/commands/dev-test-model.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,56 @@ async function waitForRuntime(manager: RuntimeManager) {
484484
logger.warn('Runtime not detected after 10 seconds.');
485485
}
486486

487+
/**
488+
* Waits until all model actions referenced by the test suites are registered
489+
* with the runtime's reflection server. This prevents 404 errors caused by
490+
* dispatching tests before the runtime has finished registering actions.
491+
*/
492+
async function waitForActions(
493+
manager: RuntimeManager,
494+
suites: TestSuite[]
495+
): Promise<void> {
496+
const requiredKeys = new Set<string>();
497+
for (const suite of suites) {
498+
if (suite.model) {
499+
const key = suite.model.startsWith('/')
500+
? suite.model
501+
: `/model/${suite.model}`;
502+
requiredKeys.add(key);
503+
}
504+
}
505+
if (requiredKeys.size === 0) return;
506+
507+
const maxAttempts = 60; // 30 seconds total
508+
const delayMs = 500;
509+
510+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
511+
try {
512+
const actions = await manager.listActions();
513+
const registeredKeys = new Set(Object.keys(actions));
514+
const missing = [...requiredKeys].filter((k) => !registeredKeys.has(k));
515+
if (missing.length === 0) {
516+
logger.info(
517+
`All ${requiredKeys.size} model actions registered. Starting tests.`
518+
);
519+
return;
520+
}
521+
if (attempt % 10 === 0 && attempt > 0) {
522+
logger.info(
523+
`Waiting for ${missing.length} model action(s) to register: ${missing.join(', ')}...`
524+
);
525+
}
526+
} catch (e) {
527+
// Actions endpoint not ready yet, keep polling.
528+
logger.debug(`Polling for actions failed, will retry: ${e}`);
529+
}
530+
await new Promise((r) => setTimeout(r, delayMs));
531+
}
532+
logger.warn(
533+
'Not all model actions registered after 30 seconds. Proceeding anyway.'
534+
);
535+
}
536+
487537
async function runTest(
488538
manager: RuntimeManager,
489539
model: string,
@@ -655,6 +705,10 @@ export const devTestModel = new Command('dev:test-model')
655705
.split(',')
656706
.map((s) => s.trim());
657707

708+
// Wait for all model actions to be registered before dispatching tests.
709+
// This prevents 404 errors when the runtime is still initializing.
710+
await waitForActions(manager, suites);
711+
658712
for (const suite of suites) {
659713
if (!suite.model) {
660714
logger.error('Model name required in test suite.');

0 commit comments

Comments
 (0)