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
- Supply both parts: 'owner/repo' with no leading or trailing slash
- Sanitize input in the UI: strip leading/trailing slashes before validation
- Split on '/' and filter empty segments, or use a stricter regex like /^[\w.-]+\/[\w.-]+$/ before calling
- 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
- Strip leading and trailing slashes from pasted values before validating
- Use a regex like /^[\w.-]+\/[\w.-]+$/ as the single source of truth for repo input
- Show the expected format as a placeholder: 'e.g. actualbudget/themes'
- Test the installer with slash-edge inputs ('/a', 'a/', '/') to keep the guard solid
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
- Invalid repo: must be in "owner/repo" format
- Cleanup group name cannot be empty
- Invalid --name: must be a non-empty string.
- No update fields provided. Use --name or --offbudget.
- Invalid cutoff date: expected a valid date (e.g. YYYY-MM-DD)
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/8ed46da6b0d11948.
Report an issue: GitHub.