Skip to content

fix(cli): sync says why it did nothing and exits non-zero - #1889

Closed
danusha2345 wants to merge 3 commits into
colbymchenry:mainfrom
danusha2345:fix/sync-reports-why-it-did-nothing
Closed

danusha2345 wants to merge 3 commits into
colbymchenry:mainfrom
danusha2345:fix/sync-reports-why-it-did-nothing

Conversation

@danusha2345

Copy link
Copy Markdown
Contributor

Symptom

When another process holds .codegraph/codegraph.lock — an MCP server (daemon or direct serve --mcp) running its own sync, or another CLI mid-index — CodeGraph.sync() catches the lock failure and returns all-zero counts. The CLI then prints Already up to date and exits 0; with --quiet (the git-hook path) it prints nothing and exits 0. Nothing was synced.

Fix

  • SyncResult gains a structured skippedReason: 'locked' (plus lockHolderPid when the lock file is readable), set only on the lock-acquisition failure path in sync(). Library / watcher callers that ignored the zero result keep working unchanged.

  • The CLI maps that reason to one stderr line and exits 1 in both modes:

    codegraph sync: Nothing synced: another process holds the index lock (PID 406506). Retry, or run "codegraph sync" when it exits.
    

    --quiet suppresses progress, never a failure — so a hook that fails the commit on exit 1 still shows the user why.

  • FileLock.readHolderPid() is a small read-only accessor so the message can name the holder; the foreign lock is left untouched.

Relation to #1842

#1842 (fix(cli): reject sync for stale extraction indexes) covers the other half of the same "sync looks successful but did nothing" symptom — an index built by an older extraction version. This PR deliberately does not duplicate that; it covers only the writer-lock case. The two are independent and can land in either order (the only shared file is src/bin/codegraph.ts, non-overlapping hunks in the same sync action).

Verification

  • npx tsc --noEmit -p tsconfig.json clean.
  • New __tests__/cli-sync-reports-reason.test.ts (5 tests): sync() reports skippedReason: 'locked' with the holder PID; codegraph sync and codegraph sync --quiet exit 1 with the reason on stderr (quiet: exactly one stderr line, empty stdout), do not print Already up to date, do not touch the lock file or the index; both modes sync normally once the lock is gone.
  • Related suites green: mcp-writer-lock, git-index-currency, sync, and every cli-*.test.ts mentioning sync — 14 files, 131 tests passed (Linux, Node 22).

🤖 Generated with Claude Code

When another process holds `.codegraph/codegraph.lock` (an MCP server or
another CLI mid-index), `CodeGraph.sync()` returned all-zero counts and
the CLI printed "Already up to date" and exited 0 — or, with `--quiet`,
printed nothing — without having synced anything.

`SyncResult` now carries `skippedReason: 'locked'` (plus the holder's PID
when readable) and the CLI maps it to one stderr line with the reason and
the remedy, exiting 1 in both normal and `--quiet` mode. Quiet suppresses
progress, never a failure.

The stale-extraction half of the same symptom is covered by colbymchenry#1842 and is
deliberately not duplicated here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…err count

The test spawns the CLI with CODEGRAPH_WASM_RELAUNCHED=1, which skips the
relaunch and with it --disable-warning=ExperimentalWarning. On Node 22 the
node:sqlite ExperimentalWarning then lands on stderr as two extra lines and
the quiet case, which expects exactly one line, fails (seen in CI on Node 22;
Node 24 no longer warns). NODE_NO_WARNINGS=1 for the spawned CLI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@inth3shadows inth3shadows left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this on Linux with Node 22, merged onto current main. cli-sync-reports-reason passes (5 of 5 runs). The git hooks codegraph installs run codegraph sync in the background with output sent to /dev/null, so the new exit 1 can't break git operations.

Two small points:

  1. Every lock failure is reported as contention. sync() catches any fileLock.acquire() failure and reports it as locked. Repro: make .codegraph/codegraph.lock a directory and run codegraph sync -q. This branch prints "Nothing synced: another process holds the index lock. Retry…" and exits 1, even though no process holds it. main exits 0. I'd set skippedReason: 'locked' only for real contention, and keep the codegraph unlock hint the original lock error gave.
  2. The changelog claim doesn't hold for the installed hooks. The entry says a failing git hook will show why. The hooks codegraph installs discard both output and exit status, so it only applies to user-written hooks.

… otherwise

Review on colbymchenry#1889: sync() reported any failure to take the index lock as
contention. A directory where codegraph.lock should be made sync print
"another process holds the index lock" and exit 1 when nothing held it.
Contention is now a live process named in the lock file (`locked`, with
its PID); any other failure is `lock-failed` and carries the lock's own
message, with its `codegraph unlock` hint. The changelog no longer says a
failing git hook shows why: the hooks codegraph installs discard both
output and exit status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@danusha2345

Copy link
Copy Markdown
Contributor Author

Thanks, both points addressed in bd957c4:

  1. Only a live holder is contention. sync() now reports locked, with the PID, only when the lock file names a process that is alive. Any other failure to take the lock, such as a directory where codegraph.lock should be or an unreadable file, is lock-failed and carries the lock's own message, codegraph unlock hint included. Your repro now prints Nothing synced: the index lock could not be taken. CodeGraph database is locked … run 'codegraph unlock' … instead of claiming another process holds it. A lock left by a dead PID is reclaimed and the sync runs, as before. Both cases are covered in cli-sync-reports-reason.test.ts, and the directory case fails without the change.
  2. Changelog. It no longer claims a failing git hook shows why. It now says a script of your own that checks the exit code can tell a skipped sync from an up-to-date one.

Full suite: 4723 passed, 11 skipped.

danusha2345 pushed a commit to danusha2345/codegraph that referenced this pull request Sep 27, 2026
… otherwise

Review on colbymchenry#1889: sync() reported any failure to take the index lock as
contention. A directory where codegraph.lock should be made sync print
"another process holds the index lock" and exit 1 when nothing held it.
Contention is now a live process named in the lock file (`locked`, with
its PID); any other failure is `lock-failed` and carries the lock's own
message, with its `codegraph unlock` hint. The changelog no longer says a
failing git hook shows why: the hooks codegraph installs discard both
output and exit status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
danusha2345 pushed a commit to danusha2345/codegraph that referenced this pull request Sep 27, 2026
@danusha2345

Copy link
Copy Markdown
Contributor Author

Closing in favour of #2014, which landed the same fix: sync now surfaces lock contention, exits 1 and names the holder. The one difference is that upstream keeps --quiet silent on purpose, which is a reasonable call. Thanks for landing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants