google-gemini/gemini-cli · critical · Error

Model policy chain must only have one `isLastResort`.

Error message

Model policy chain must only have one `isLastResort`.

What it means

Thrown by validateModelPolicyChain() when more than one entry in the chain has isLastResort set to true. The system expects exactly one terminal fallback so that recovery logic is deterministic; multiple last-resort models create ambiguity about which to use when all primary models fail. This is a structural invariant of the policy chain.

Source

Thrown at packages/core/src/availability/policyCatalog.ts:161

 * Provides a default policy scaffold for models not present in the catalog.
 */
export function createDefaultPolicy(
  model: string,
  options?: { isLastResort?: boolean },
): ModelPolicy {
  return definePolicy({ model, isLastResort: options?.isLastResort });
}

export function validateModelPolicyChain(chain: ModelPolicyChain): void {
  if (chain.length === 0) {
    throw new Error('Model policy chain must include at least one model.');
  }
  const lastResortCount = chain.filter((policy) => policy.isLastResort).length;
  if (lastResortCount === 0) {
    throw new Error('Model policy chain must include an `isLastResort` model.');
  }
  if (lastResortCount > 1) {
    throw new Error('Model policy chain must only have one `isLastResort`.');
  }
}

/**
 * Helper to define a ModelPolicy with default actions and state transitions.
 * Ensures every policy is a fresh instance to avoid shared state.
 */
function definePolicy(config: PolicyConfig): ModelPolicy {
  return {
    model: config.model,
    isLastResort: config.isLastResort,
    maxAttempts: config.maxAttempts,
    actions: { ...DEFAULT_ACTIONS, ...(config.actions ?? {}) },
    stateTransitions: {
      ...DEFAULT_STATE,
      ...(config.stateTransitions ?? {}),
    },
  };

View on GitHub (pinned to 5024443c72)

Solutions

  1. Ensure exactly one entry has isLastResort: true; clear the flag on all others.
  2. When merging chains, strip isLastResort from the first chain before appending a chain that already has one.
  3. Use createDefaultPolicy(model) (without isLastResort) for primary models and reserve the flag for the single fallback.
  4. After building programmatically, run: const lr = chain.filter(p => p.isLastResort); if (lr.length > 1) lr.slice(1).forEach(p => p.isLastResort = false).

Example fix

// before — two last-resort entries
const chain = [
  definePolicy({ model: 'gemini-pro', isLastResort: true }),
  definePolicy({ model: 'gemini-flash', isLastResort: true }),
];
validateModelPolicyChain(chain); // throws

// after — only the true fallback is marked
const chain = [
  definePolicy({ model: 'gemini-pro' }),
  definePolicy({ model: 'gemini-flash', isLastResort: true }),
];
Defensive patterns

Strategy: validation

Validate before calling

// Ensure exactly one last-resort entry
const lastResortIndices = chain
  .map((p, i) => (p.isLastResort ? i : -1))
  .filter((i) => i >= 0);
if (lastResortIndices.length > 1) {
  // Keep only the first, clear the rest
  for (let i = 1; i < lastResortIndices.length; i++) {
    chain[lastResortIndices[i]].isLastResort = false;
  }
}
validateModelPolicyChain(chain);

Type guard

function chainHasSingleLastResort(chain: ModelPolicyChain): boolean {
  return chain.filter((p) => p.isLastResort).length === 1;
}

Try / catch

try {
  validateModelPolicyChain(chain);
} catch (e) {
  if (e instanceof Error && e.message.includes('only have one')) {
    // Deduplicate: keep the last last-resort entry, clear others
    const lrs = chain.filter((p) => p.isLastResort);
    lrs.slice(0, -1).forEach((p) => (p.isLastResort = false));
    validateModelPolicyChain(chain);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling validateModelPolicyChain(chain) where two or more entries have isLastResort: true. For example, [{ model: 'a', isLastResort: true }, { model: 'b', isLastResort: true }].

Common situations: Concatenating two chains (e.g., custom + default) where both already contain a last-resort entry; marking every model as isLastResort defensively; merging chains from multiple config sources without deduplication; copying a policy object and accidentally duplicating the flag.

Related errors


AI-assisted analysis of google-gemini/gemini-cli@5024443c72 (2026-08-12). Data as JSON: /api/errors/90715d9797b52145. Report an issue: GitHub.