affaan-m/ECC · error · Error

Invalid repo format: "${repo}". Expected "owner/repo".

Error message

Invalid repo format: "${repo}". Expected "owner/repo".

What it means

Thrown by normalizeRepo in gh-api.js when the repo string does not split into exactly two non-empty parts (owner and name) on '/'. All gh CLI calls in this module build --repo owner/name from these parts, so a malformed repo would produce an invalid gh invocation. Empty segments are filtered, so 'a//b' and '/a/b/' both normalize to ['a','b'] and are accepted.

Source

Thrown at scripts/lib/github-coordination/gh-api.js:8

'use strict';

const { spawnSync } = require('child_process');

function normalizeRepo(repo) {
  const parts = String(repo || '').split('/').filter(Boolean);
  if (parts.length !== 2) {
    throw new Error(`Invalid repo format: "${repo}". Expected "owner/repo".`);
  }
  const [owner, name] = parts;
  return { owner, name };
}

function normalizeIssueNumber(value) {
  const parsed = Number.parseInt(String(value), 10);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    throw new Error(`Invalid issue number: ${value}`);
  }
  return parsed;
}

function normalizeLabelValue(label) {
  if (typeof label === 'string') {
    return label.trim();
  }
  if (label && typeof label === 'object') {

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Pass repo strictly as 'owner/name' with exactly one slash and two non-empty segments.
  2. If you have a URL, extract owner/name first: new URL(remoteUrl).pathname.slice(1).replace(/\/$/, '').
  3. For SSH remotes (git@github.com:owner/name.git), strip host and .git then split.
  4. Add a normalizeRepo call at your config-load boundary so downstream code always gets owner/name.

Example fix

// before
getIssue('https://github.com/acme/widgets', 42);

// after — extract owner/name from any GitHub reference
function toOwnerName(input) {
  if (/^[\w.-]+\/[\w.-]+$/.test(input)) return input;
  const u = new URL(input.includes('://') ? input : `https://github.com/${input}`);
  return u.pathname.replace(/^\//, '').replace(/\.git$/, '').replace(/\/$/, '');
}
getIssue(toOwnerName(raw), 42);
Defensive patterns

Strategy: validation

Validate before calling

const parts = String(repo || '').split('/').filter(Boolean);
if (parts.length !== 2) {
  throw new Error(`repo must be 'owner/name', got ${JSON.stringify(repo)}`);
}

Type guard

function isOwnerName(repo) {
  return /^[\w.-]+\/[\w.-]+$/.test(String(repo || ''));
}

Try / catch

try {
  getIssue(repo, issueNumber);
} catch (e) {
  if (/Invalid repo format/.test(e.message)) { console.error('Pass repo as owner/name'); process.exit(2); }
  throw e;
}

Prevention

When it happens

Trigger: Passing repo 'owner' (missing /name), 'owner/name/extra' (three parts), 'a/b/c/d', empty string, or null; a full URL like https://github.com/owner/name (splits into >2 parts).

Common situations: User passed a bare repo name; passed a full GitHub URL instead of owner/name; config stored the SSH/HTTPS remote URL; trailing/extra slashes.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/35f3224247d0c747. Report an issue: GitHub.