can1357/oh-my-pi · error

Invalid ${scheme}:// list state '${stateRaw}'. Expected one

Error message

Invalid ${scheme}:// list state '${stateRaw}'. Expected one of: ${allowedStates.join(", ")}.

What it means

parseListOptions validates the ?state= query parameter for issue:// and pr:// list URLs. PRs accept open|closed|merged|all; issues accept open|closed|all. An unknown value throws instead of silently falling back to 'open', because the fallback would be indistinguishable from 'no matches'.

Source

Thrown at packages/coding-agent/src/internal-urls/issue-pr-protocol.ts:83

	limit: number;
	author: string | undefined;
	label: string | undefined;
}

type Parsed = ParsedSingle | ParsedList | ParsedPrDiff;

const LIST_LIMIT_DEFAULT = 30;
const LIST_LIMIT_MAX = 100;

function parseListOptions(url: InternalUrl, scheme: Scheme, repo: string | undefined): ParsedList {
	const stateRaw = url.searchParams.get("state");
	const allowedStates: ParsedList["state"][] =
		scheme === "pr" ? ["open", "closed", "merged", "all"] : ["open", "closed", "all"];
	if (stateRaw !== null && !(allowedStates as string[]).includes(stateRaw)) {
		// Reject instead of silently falling back to "open": a typo'd state
		// would otherwise return the open list, indistinguishable from "no
		// matches for the requested state".
		throw new Error(`Invalid ${scheme}:// list state '${stateRaw}'. Expected one of: ${allowedStates.join(", ")}.`);
	}
	const state = (stateRaw ?? "open") as ParsedList["state"];

	const limitRaw = url.searchParams.get("limit");
	let limit = LIST_LIMIT_DEFAULT;
	if (limitRaw !== null) {
		const parsed = parsePositiveDecimalInt(limitRaw);
		if (parsed === undefined) {
			throw new Error(
				`Invalid ${scheme}:// list limit '${limitRaw}'. Expected a positive integer (max ${LIST_LIMIT_MAX}).`,
			);
		}
		limit = Math.min(parsed, LIST_LIMIT_MAX);
	}
	return {
		kind: "list",
		repo,
		state,

View on GitHub (pinned to 9690622007)

Solutions

  1. Use a lowercase allowed value: open, closed, merged, all for pr://; open, closed, all for issue://
  2. Drop ?state= entirely to get the 'open' default explicitly
  3. Fix casing in generated URLs (state is compared case-sensitively)

Example fix

// before
resolve('pr://org/repo?state=Merged')
// after
resolve('pr://org/repo?state=merged')
Defensive patterns

Strategy: validation

Validate before calling

const PR_STATES = ['open','closed','merged','all']
const ISSUE_STATES = ['open','closed','all']
const allowed = scheme === 'pr' ? PR_STATES : ISSUE_STATES
if (state !== undefined && !allowed.includes(state)) throw new Error(`bad ${scheme} state: ${state}`)

Type guard

const isPrState = (s: string): s is 'open'|'closed'|'merged'|'all' =>
  ['open','closed','merged','all'].includes(s)

Prevention

When it happens

Trigger: Calling pr://...?state=Merged (case-sensitive) or issue://...?state=merged (invalid for issues) via parseUrl.

Common situations: Case mistakes ('Open' vs 'open'); using the PR-only 'merged' state on issue://; typos like 'closes'.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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