can1357/oh-my-pi · error · ToolError

invalid issue identifier: ${identifier}. Pass an issue numbe

Error message

invalid issue identifier: ${identifier}. Pass an issue number or URL.

What it means

getOrFetchIssue resolves a GitHub issue number or URL to issue view data, shared by the `github` tool op and the `issue://` protocol. It rejects identifiers beginning with '-' because a leading dash is indistinguishable from a CLI flag / negation and can never be a valid issue number or URL. The throw happens before any network call, so it is a cheap input guard.

Source

Thrown at packages/coding-agent/src/tools/gh-view.ts:543

	const args = ["pr", "view", String(number)];
	appendRepoFlag(args, repo, String(number));
	args.push("--json", (includeComments ? GH_PR_FIELDS : GH_PR_FIELDS_NO_COMMENTS).join(","));
	const data = await github.json<GhPrViewData>(cwd, args, signal, { repoProvided: true });
	if (includeComments && typeof data.number === "number") {
		data.reviewComments = await fetchPrReviewComments(cwd, repo, data.number, signal);
	}
	const rendered = formatPrView(data, { pr: String(number), repo, comments: includeComments });
	return { rendered, sourceUrl: data.url, payload: data };
}

/**
 * Cache-aware issue/view fetcher. Used by both the `github` tool op and the
 * `issue://` protocol handler so a single shared row services both surfaces.
 */
export async function getOrFetchIssue(options: IssueViewLookupOptions): Promise<ViewLookupResult<GhIssueViewData>> {
	const identifier = requireNonEmpty(options.issue, "issue");
	if (identifier.startsWith("-")) {
		throw new ToolError(`invalid issue identifier: ${identifier}. Pass an issue number or URL.`);
	}
	const includeComments = options.includeComments ?? true;
	const authKey = options.cacheAuthKey === undefined ? (resolveGithubCacheAuthKey() ?? null) : options.cacheAuthKey;
	const urlParse = parseIssueUrl(identifier);
	// Prefer the URL's repo when the identifier is a full URL; fall back to the
	// explicit `repo` option, then to the cwd's default repo.
	let repo = urlParse.repo ?? normalizeOptionalString(options.repo);
	let cacheNumber = urlParse.issueNumber;
	if (cacheNumber === undefined) {
		cacheNumber = parsePositiveDecimalInt(identifier);
	}
	if (cacheNumber !== undefined && !repo) {
		try {
			repo = await resolveDefaultRepoMemoized(options.cwd, options.signal);
		} catch {
			// Resolution failure leaves `repo` undefined: we'll fall through to a
			// direct fetch below so gh produces its own error message instead of
			// us masking it with a friendlier one.

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass a positive issue number ('123') or a full issue URL ('https://github.com/owner/repo/issues/123')
  2. Strip stray leading dashes or flags from the identifier before calling
  3. Validate the identifier is non-empty, does not start with '-', and parses as a number or GitHub issue URL

Example fix

// before
await getOrFetchIssue({ issue: "-42" });
// after
await getOrFetchIssue({ issue: "42" });
Defensive patterns

Strategy: validation

Validate before calling

function isValidIssueIdentifier(id) {
  return typeof id === "string" && id.length > 0 && !id.startsWith("-") && (/^\d+$/.test(id) || /^https:\/\/[^/]+\/.+\/issues\/\d+/.test(id));
}
if (!isValidIssueIdentifier(issue)) throw new Error(`bad issue identifier: ${issue}`);

Type guard

const isIssueIdentifier = (v: unknown): v is string =>
  typeof v === "string" && v.length > 0 && !v.startsWith("-") && (/^\d+$/.test(v) || /^https:\/\//.test(v));

Try / catch

try {
  await getOrFetchIssue({ issue });
} catch (err) {
  if (err instanceof ToolError && err.message.startsWith("invalid issue identifier")) {
    // surface a corrected prompt or fall back to listing issues
  } else throw err;
}

Prevention

When it happens

Trigger: Calling getOrFetchIssue({ issue: '-123' }) or the github/issue:// surface with a value like '-' or '-x' — typically a mangled negative number, a leaked flag argument, or an empty/partial identifier after string slicing.

Common situations: Scripts that join argv and accidentally pass a flag ('-v') as the issue; templated prompts that interpolate an unset variable rendering as '-'; users pasting truncated URLs losing everything before the dash.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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