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
- Pass a positive issue number ('123') or a full issue URL ('https://github.com/owner/repo/issues/123')
- Strip stray leading dashes or flags from the identifier before calling
- 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
- Validate issue identifiers against /^\d+$/ or a GitHub issue URL regex before calling
- Trim and sanitize CLI/argv input; never forward flags as identifiers
- Check interpolated template variables are defined before building the identifier
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
- path must be repository-relative
- invalid state: {state!r}
- invalid repo {repo!r}
- err.to_string() (timestamp parse error)
- duration is too large: {value}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/cfdf5096cfa6c8ed.
Report an issue: GitHub.