feat(cli): auto-detect GitHub shorthand (owner/repo) in positional arguments#1628
feat(cli): auto-detect GitHub shorthand (owner/repo) in positional arguments#1628serhiizghama wants to merge 4 commits into
Conversation
…dule Keeps shorthand pattern matching available to the CLI entrypoint without pulling in the heavy git-url-parse dependency, following the same approach as isExplicitRemoteUrl. Re-exported from gitRemoteParse to preserve the public API.
…guments Allow running `repomix owner/repo` without the --remote flag. Shorthand is ambiguous with relative local paths, so it is only treated as remote when the argument does not exist as a local path and the repository is confirmed reachable on GitHub via a HEAD-only `git ls-remote` probe. A mistyped local path such as `src/uitls` fails the probe and falls through to the regular local-path error instead of triggering an unintended clone attempt.
|
Worried about impact? Review this PR in Change Stack to explore blast radius before you approve or request changes. Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe PR adds GitHub shorthand ( ChangesGitHub Shorthand Auto-Detection for CLI Arguments
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces automatic detection of GitHub repository shorthands (owner/repo) in CLI positional arguments. When a shorthand is detected and does not exist as a local path, a lightweight HEAD-only git ls-remote probe is used to verify its existence on GitHub before triggering a remote action. The reviewer feedback focuses on improving robustness: disabling interactive Git Credential Manager prompts and reducing the probe timeout to 5 seconds, ensuring absolute paths (especially on Windows) are not misidentified as shorthands, and explicitly checking for ENOENT errors during local path verification to prevent permission errors from triggering remote probes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| validateGitUrl(url); | ||
|
|
||
| try { | ||
| const result = await deps.execFileAsync('git', ['ls-remote', '--', url, 'HEAD'], gitRemoteOpts); |
There was a problem hiding this comment.
When probing for repository existence, we should ensure that Git Credential Manager (GCM) does not trigger interactive GUI prompts/popups if the repository is private or does not exist. This is especially important for auto-detection, as a simple typo in a local path (e.g., repomix src/uitls) could otherwise trigger an annoying login popup.\n\nAdditionally, the default gitRemoteOpts timeout is 30 seconds. For an automatic background probe triggered by a potential typo, waiting 30 seconds on a slow or offline network before falling back to the local path error is a poor user experience. Reducing the timeout to 5 seconds keeps the CLI responsive.
const result = await deps.execFileAsync('git', ['ls-remote', '--', url, 'HEAD'], {\n ...gitRemoteOpts,\n timeout: 5000,\n env: { ...gitRemoteOpts.env, GCM_INTERACTIVE: 'never' },\n });There was a problem hiding this comment.
Good catch — addressed in 3f65dad. The probe now uses dedicated opts: 5s timeout and GCM_INTERACTIVE: 'never' on top of the existing GIT_TERMINAL_PROMPT: '0', so a mistyped path never hangs on slow networks or triggers credential manager popups.
| // A mistyped local path (e.g. `src/uitls`) fails the probe and falls through to the | ||
| // regular local-path handling instead of triggering an unintended clone attempt. | ||
| // Skipped in stdin mode, where positional directory arguments are rejected. | ||
| if (directories.length === 1 && !options.stdin && isValidShorthand(directories[0])) { |
There was a problem hiding this comment.
On Windows, absolute paths can be specified using forward slashes (e.g., C:/project). Such paths will match the isValidShorthand regex (since C is a valid owner pattern and project is a valid repo pattern). If the specified absolute path does not exist locally, the CLI would incorrectly attempt to probe it as a GitHub shorthand (e.g., querying https://github.com/C/project.git).\n\nTo prevent this, we should explicitly ensure the argument is not an absolute path before performing shorthand auto-detection.
| if (directories.length === 1 && !options.stdin && isValidShorthand(directories[0])) { | |
| if (directories.length === 1 && !options.stdin && !path.isAbsolute(directories[0]) && isValidShorthand(directories[0])) { |
There was a problem hiding this comment.
C:/project can't actually match the shorthand pattern: VALID_NAME_PATTERN ([a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?) doesn't allow :, so the owner part C: fails the regex — same for POSIX absolute paths, which start with /. Added a regression test (should not treat Windows-style absolute path as shorthand) in 3f65dad to lock this in.
| const localPathExists = await fs.access(path.resolve(cwd, directories[0])).then( | ||
| () => true, | ||
| () => false, | ||
| ); |
There was a problem hiding this comment.
If a local path exists but is inaccessible due to permission restrictions (e.g., EACCES or EPERM), fs.access will reject. Currently, any rejection is treated as "path does not exist", which would cause the CLI to incorrectly perform a remote GitHub probe for a local path that actually exists.\n\nWe should explicitly check if the error code is ENOENT to ensure we only fall back to the remote probe when the path is truly missing.
const localPathExists = await fs.access(path.resolve(cwd, directories[0])).then(\n () => true,\n (err: any) => err?.code !== 'ENOENT',\n );There was a problem hiding this comment.
Fixed in 3f65dad — fs.access rejection is now only treated as "missing" for ENOENT/ENOTDIR; EACCES/EPERM keep local-path precedence. Covered by a new test with a mocked EACCES rejection.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/cli/cliRun.ts`:
- Around line 309-312: The current fs.access usage treats any error as "missing"
which misclassifies permission-denied errors (EACCES/EPERM); update the local
path existence check around fs.access(path.resolve(cwd, directories[0])) so that
it distinguishes error.code === 'EACCES' || error.code === 'EPERM' and returns
true (treat as local) for those cases while still returning false for ENOENT;
adjust the Promise.then/.catch or switch to try/catch around await fs.access in
the same block that sets localPathExists to ensure permission errors are
considered existing paths.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 8d51b476-79ba-4f72-b36f-62bda0ae313a
📒 Files selected for processing (8)
src/cli/cliRun.tssrc/core/git/gitCommand.tssrc/core/git/gitRemoteHandle.tssrc/core/git/gitRemoteParse.tssrc/core/git/gitRemoteUrl.tstests/cli/cliRun.test.tstests/core/git/gitCommand.test.tstests/core/git/gitRemoteHandle.test.ts
| const localPathExists = await fs.access(path.resolve(cwd, directories[0])).then( | ||
| () => true, | ||
| () => false, | ||
| ); |
There was a problem hiding this comment.
Treat permission-denied paths as local, not “missing”.
On Line 309, any fs.access failure is treated as non-existent. EACCES/EPERM means the path exists but is inaccessible, and this can incorrectly trigger GitHub probing and remote execution, breaking local-path precedence.
Suggested fix
- const localPathExists = await fs.access(path.resolve(cwd, directories[0])).then(
- () => true,
- () => false,
- );
+ const resolvedInputPath = path.resolve(cwd, directories[0]);
+ const localPathExists = await fs.access(resolvedInputPath).then(
+ () => true,
+ (error: NodeJS.ErrnoException) => !['ENOENT', 'ENOTDIR'].includes(error.code ?? ''),
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const localPathExists = await fs.access(path.resolve(cwd, directories[0])).then( | |
| () => true, | |
| () => false, | |
| ); | |
| const resolvedInputPath = path.resolve(cwd, directories[0]); | |
| const localPathExists = await fs.access(resolvedInputPath).then( | |
| () => true, | |
| (error: NodeJS.ErrnoException) => !['ENOENT', 'ENOTDIR'].includes(error.code ?? ''), | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/cli/cliRun.ts` around lines 309 - 312, The current fs.access usage treats
any error as "missing" which misclassifies permission-denied errors
(EACCES/EPERM); update the local path existence check around
fs.access(path.resolve(cwd, directories[0])) so that it distinguishes error.code
=== 'EACCES' || error.code === 'EPERM' and returns true (treat as local) for
those cases while still returning false for ENOENT; adjust the
Promise.then/.catch or switch to try/catch around await fs.access in the same
block that sets localPathExists to ensure permission errors are considered
existing paths.
There was a problem hiding this comment.
Fixed in 3f65dad — rejection is now treated as "missing" only for ENOENT/ENOTDIR, so permission-denied paths keep local precedence. Test added.
…robe fail fast Review follow-up: EACCES/EPERM from fs.access means the path exists but is inaccessible, so it must keep local-path precedence instead of triggering a remote probe. The probe itself now uses a 5s timeout and GCM_INTERACTIVE=never so a mistyped path never hangs on slow networks or credential manager popups.
Motivation
Follow-up to #1120. The explicit URL part shipped in v1.12.0 (#1145), and the remaining piece is the
owner/reposhorthand — held back by a fair concern:This PR is a concrete proposal for that remaining piece, designed so the typo scenario can never reach a clone attempt.
What this changes
repomix yamadashy/repomixnow works without--remote. The shorthand is treated as remote only when all three hold:owner/repopattern (sameisValidShorthandused by--remote).git ls-remoteprobe before any clone/download starts.A mistyped local path like
src/uitlsfails the probe and falls through to the regular "Target path does not exist" error — exactly what happens today. When detection does kick in, it prints an explicit notice with an escape hatch:The probe only runs in the case that would otherwise be guaranteed to fail (single arg, shorthand-shaped, no such local path), so no network cost is added to any currently-working invocation. Detection is skipped in
--stdinmode, which rejects positional directory arguments.Why this approach
git ls-remote -- <url> HEAD(newexecLsRemoteHead) rather than the existing--heads --tagsvariant, so the response stays tiny even for repositories with thousands of refs.GIT_TERMINAL_PROMPT=0is already set for remote git commands, so there is no interactive hang. Private-repo users keep using explicit--remote owner/repo.isValidShorthandmoved into the lightweightgitRemoteUrl.tsmodule (same pattern asisExplicitRemoteUrl, re-exported fromgitRemoteParse.ts) so the CLI entrypoint doesn't pull ingit-url-parseat startup. Public API is unchanged.ls-remoteavoids rate limits and extra HTTP plumbing, and respects existing git credential helpers.Testing
cliRun.test.ts: reachable shorthand → remote action; unreachable shorthand → local handling; existing local path that matches the pattern (src/cli) → local handling with no probe;--stdin→ no probe.execLsRemoteHead(HEAD-only args, error propagation, dangerous-URL rejection) andcheckRemoteRepoExists(true/false/validation).repomix sindresorhus/is-plain-objdetects and packs via archive download; a non-existentowner/repoand an existing localsrc/utilsdirectory both behave exactly as before.Closes #1120