actualbudget/actual · error

Invalid repo: must include both owner and repo name

Error message

Invalid repo: must include both owner and repo name

What it means

This is the second validation in normalizeGitHubRepo: after splitting the trimmed input on '/', both the owner segment and the repo name segment must be non-empty. Input like '/repo', 'owner/', or '//' produces empty parts and triggers 'Invalid repo: must include both owner and repo name'.

Source

Thrown at packages/desktop-client/src/style/customThemes.ts:53

/**
 * Normalize a GitHub repo identifier to a full GitHub URL.
 * Accepts "owner/repo" format.
 * Returns "https://github.com/owner/repo".
 * @throws {Error} If repo is invalid or missing owner/repo.
 */
export function normalizeGitHubRepo(repo: string): string {
  const trimmed = repo.trim();
  if (!trimmed.includes('/')) {
    throw new Error('Invalid repo: must be in "owner/repo" format');
  }

  const parts = trimmed.split('/');
  const owner = parts[0]?.trim();
  const repoName = parts[1]?.trim();

  if (!owner || !repoName) {
    throw new Error('Invalid repo: must include both owner and repo name');
  }

  return `https://github.com/${owner}/${repoName}`;
}

/**
 * Try fetching actual.css from main branch.
 */
export function fetchThemeCss(repo: string): Promise<string> {
  const url = new URL(
    `https://raw.githubusercontent.com/${repo}/refs/heads/main/actual.css`,
  );
  url.searchParams.set('v', Date.now().toString());

  return fetchDirectCss(url.toString());
}

/**

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Supply both parts: 'owner/repo' with no leading or trailing slash
  2. Sanitize input in the UI: strip leading/trailing slashes before validation
  3. Split on '/' and filter empty segments, or use a stricter regex like /^[\w.-]+\/[\w.-]+$/ before calling
  4. Show inline field validation so users see the expected format immediately

Example fix

// before
normalizeGitHubRepo('/actualbudget/themes'); // throws
// after
const cleaned = input.trim().replace(/^\/+|\/+$/g, '');
if (!/^[\w.-]+\/[\w.-]+$/.test(cleaned)) {
  throw new Error('Enter a repo as owner/repo');
}
normalizeGitHubRepo(cleaned);
Defensive patterns

Strategy: validation

Validate before calling

const isValidOwnerRepo = (input: string): boolean => {
  const [owner = '', repo = ''] = input.trim().split('/');
  return owner.trim() !== '' && repo.trim() !== '';
};
// gate: if (!isValidOwnerRepo(input)) showInlineError('Both owner and repo name are required');

Type guard

const hasOwnerAndRepo = (parts: readonly (string | undefined)[]): parts is [string, string, ...string[]] =>
  parts.length >= 2 && !!parts[0]?.trim() && !!parts[1]?.trim();

Try / catch

try {
  const url = normalizeGitHubRepo(input);
} catch (err) {
  if (err.message.includes('must include both owner and repo name')) {
    setFieldError('Remove leading/trailing slashes: use owner/repo');
  } else throw err;
}

Prevention

When it happens

Trigger: Calling normalizeGitHubRepo with strings such as '/my-theme', 'my-theme/', 'a/b/' edge inputs, or a value beginning/ending with a slash after trim — often from copy-paste mistakes in the custom theme installer.

Common situations: Pasting 'https://github.com/' without the path; a leading slash from path-relative copying; trailing slashes from URL copies; whitespace-only extra segments.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/8ed46da6b0d11948. Report an issue: GitHub.