can1357/oh-my-pi · error · Error

No tiny/smol model available for AI staging

Error message

No tiny/smol model available for AI staging

What it means

aiStage routes its diff-grouping request through a lightweight model; it resolves the configured "tiny" or "smol" role selection from settings against the registry's available models. If no model fills either role, it throws instead of silently failing later during completion.

Source

Thrown at packages/coding-agent/src/cli/git-tui/ai-stage.ts:88

 * stage the matching hunks. Called by the git TUI's unstaged-header wand pill.
 * @throws when no model/key resolves, git fails, or every judgement in a pass errors.
 */
export async function aiStage(options: AiStageOptions): Promise<AiStageOutcome> {
	const { cwd, instruction, signal, onProgress } = options;
	const repo = vcs.requireGit(cwd);
	const untracked = options.files.filter(file => file.kind === "untracked");
	const tracked = options.files.filter(file => file.kind !== "untracked" && file.kind !== "conflicted");
	if (tracked.length === 0 && untracked.length === 0) throw new Error("No unstaged changes to filter");

	onProgress?.("Resolving model…");
	const settings = await Settings.init({ cwd });
	const authStorage = await discoverAuthStorage();
	try {
		const registry = new ModelRegistry(authStorage);
		await registry.refresh();
		await loadCliExtensionProviders(registry, settings, cwd);
		const model = resolveRoleSelection(["tiny", "smol"], settings, registry.getAvailable())?.model;
		if (!model) throw new Error("No tiny/smol model available for AI staging");
		if (!(await registry.getApiKey(model))) throw new Error(`No API key for ${model.provider}/${model.id}`);
		const complete = createCompleter(model, registry.resolver(model), signal);

		const rawDiff = tracked.length > 0 ? await repo.diffText({ files: tracked.map(file => file.path) }, signal) : "";
		const fileDiffs = new Map(parseFileDiffs(rawDiff).map(entry => [entry.filename, entry]));

		interface Candidate {
			file: Pick<ChangedFile, "path" | "kind">;
			/** Parsed worktree diff; absent for untracked files. */
			diff?: FileDiff;
		}
		const candidates: Candidate[] = tracked.flatMap(file => {
			const diff = fileDiffs.get(file.path);
			return diff ? [{ file, diff }] : [];
		});
		candidates.push(...untracked.map(file => ({ file })));

		// File pass: one completion sees the whole (batched) list, so files are

View on GitHub (pinned to 9690622007)

Solutions

  1. Configure a tiny or smol model in settings (role selection) for AI staging.
  2. Ensure a model catalog/provider is configured so the registry has available models.
  3. Check provider exclude/filters in settings that may be hiding eligible models.

Example fix

// before (settings)
{}
// after (settings)
{ "roles": { "smol": "anthropic/claude-3-5-haiku" } }
Defensive patterns

Strategy: validation

Validate before calling

const settings = await Settings.init({ cwd });
const registry = new ModelRegistry(await discoverAuthStorage());
await registry.refresh();
const hasModel = !!resolveRoleSelection(["tiny", "smol"], settings, registry.getAvailable())?.model;
if (!hasModel) { /* fall back to manual staging or configure a model */ }

Try / catch

try {
  await aiStage(opts);
} catch (e) {
  if (e instanceof Error && e.message.includes("No tiny/smol model available")) {
    await manualStage(); // fallback path
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling aiStage when settings define no tiny/smol role model and no available model in the registry matches the tiny or smol role.

Common situations: Fresh installs with default/empty settings, models.json filtered down by provider excludes, or an environment where all candidate models were disabled.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/85af0483c9ba2b21. Report an issue: GitHub.