fix(orbit-local): friendly errors for non-git and empty-git index paths
What does this MR do and why?
Running orbit index against a directory with no git repository reported success: exit 0,
no output, no database created, and the MCP index tool returned an empty success the same
way. Pointing it at a repository with no commits leaked a raw git error with advice about
-- path separation that misleads a first-time user. Both cases now fail with a one-line
error naming the condition and what to do, the two fixes proposed in #658 (closed).
Related Issues
Relates to #1010 (this completes its folded-in #658 (closed) item: friendly errors for non-git and empty-git directories passed to index).
Testing
Three new CLI integration tests: orbit index on a directory with a source file but no git
repository must fail and name the condition; the MCP index tool on the same shape must
return a recoverable tool error (isError: true) rather than an empty success; and index on
a freshly initialized repository with no commits must fail with the friendly message and
must not leak the raw ambiguous argument git error. All three fail on current main and
pass with this change. The rest of the local CLI suite passes except one repo-map fixture
assertion that fails identically on unmodified main in the same environment (macOS arm64),
so it is unrelated to this change.
Performance Analysis
No hot path changed: an early return on the empty case became an early error.
- This merge request does not introduce any performance regression. If a performance regression is expected, explain why.
Agent context — long-form analysis, file-by-file walkthroughs, profiler output, alternatives considered
Reproduced on macOS arm64 and Windows Server 2022 with v0.95.1: mkdir a directory, add a
source file, orbit index <dir> exits 0 silently and creates no database; orbit sql then
errors with "no local graph found. Index a repository first".
Root cause chain:
index_collectincrates/orbit-local/src/main.rsresolves repositories viaWorkspace::resolve_repos, and on an empty result loggedinfo!("No git repositories found in {}")and returnedOk(Vec::new()).- The CLI's default log level is
WARN(DEBUGonly under-v), so thatinfo!never reached stderr in a default run. - Schema initialization happens after that early return, so no DuckDB file was created, which is what makes the downstream "index a repository first" error misdirecting.
- The MCP
indextool (crates/orbit-local/src/mcp.rs) callsindex_collectdirectly and serialized the empty success as[]withisError: false, so an agent could not detect the condition either.
The non-git fix replaces the early return with anyhow::bail! at that single point,
covering both the CLI and the MCP tool. The error wording ("Pass a repository path, or a
directory containing one.") deliberately differs from #658 (closed)'s draft ("is not inside a Git
repository"): index also accepts a directory containing one or more repositories, so the
message mirrors the command's own prompt description in config/prompts/local/index.yml
instead. That file is untouched (its text makes no claim about the empty case, so no prompt
version bump is needed). Happy to adopt #658 (closed)'s wording if preferred.
The no-commits fix sits in git_info: when the commit-hash fetch fails, a
git rev-parse --quiet --verify HEAD probe classifies the empty-repository case and
replaces the raw gitalisk error with #658 (closed)'s proposed message verbatim ("has no commits yet.
Make at least one commit before indexing."), before the error reaches the indexing loop's
per-repository skip logging. Other failure shapes keep the existing error chain unchanged.
This also improves repo-map, which resolves git metadata through the same function.
Alternatives considered: a tracing::warn! (visible at the default level) without a failure
exit was rejected because #658 (closed) explicitly proposed a non-zero exit, and a success exit code
leaves the MCP path indistinguishable from a real empty success. The upstream
gitalisk-core index_repositories already returns repository statistics that the caller
discards; deriving the message from that count was unnecessary for this fix and would have
widened the diff.
Interaction with !2194 (merged)'s failure capture (rebased onto): the no-commits reason now flows
into the _orbit_manifest error row
that work introduced, so orbit list shows "has no commits yet. Make at least one commit
before indexing." instead of the raw git chain (verified by query against a built binary).
The new unindexable_repo_records_error_in_manifest test uses a commitless repository as
its fixture and passes unchanged with this branch, since it asserts a non-empty reason, not
specific text.
Travels with !2203 (merged), which documents the script-free Windows install path: that page change
sends more first-run users into orbit index, so this fix should land with or before it.
Scope note: this covers both cases in #658 (closed), so its checkbox on #1010 can close; the other three folded items there (#642 (closed), #653 (closed), #695 (closed)) are separate subsystems and are untouched. The non-git case was reproduced on macOS arm64 and Windows Server 2022 with released v0.95.1; the no-commits case follows #658 (closed)'s own reproduction and was verified against this branch's build (friendly message on stderr, exit 1, no raw git advice).
The integration tests follow the existing patterns in
crates/integration-tests/tests/cli.rs: schema_errors_when_db_missing for the CLI surface
(tempdir, run_cmd, assert on exit status and a stderr substring) and
mcp_bad_sql_is_recoverable_tool_error for the MCP surface (mcp_roundtrip, assert
isError and the message text).
This MR was prepared with assistance from Claude Code; the change and tests were verified by running the CLI integration suite locally.