The cache should answer routine agent queries without network access:
- search issues, pull requests, comments, source notes, and wiki-like pages;
- get a record by legacy id, path, remote id, or URL;
- resolve backlinks;
- explain sync status and conflicts;
- export deterministic snapshots;
- compare local/export/remote state when remote data is available.
Start with SQLite plus optional deterministic markdown/JSON exports.
Candidate tables:
records: normalized issues, pull requests, comments, pages, and sources.full_text: search text and extracted headings.identity_map: legacy id, local path, remote id, remote URL, aliases.links: source record, target record, link kind, raw link text, resolved target.remote_revisions: remote revision metadata and fetch timestamps.sync_events: sync command, idempotency key, result, error class, evidence path.conflicts: local value, remote value, conflict class, resolution state.embedding_namespaces,chunk_embeddings, andrag_index_runs: model-scoped RAG coverage and indexing progress state.
- Reads are cache-first.
- Writes are explicit commands.
- Every remote write has an idempotency key or deterministic replay guard.
- Every sync creates reviewable evidence.
- Remote ids are aliases. Legacy ids remain stable migration keys.
- Failed writes stay visible until retried or deliberately dismissed.
The cache is optimized for agent-side fan-out reads. Routine read operations such as list, get, search, status, export, diff, and MCP read tools must not require the process-wide writer lock when the SQLite schema is already current.
The writer lock is reserved for operations that mutate cache state or need exclusive migration admission:
- schema initialization and supported schema upgrades;
- sync and index refresh operations;
- explicit write commands and cache-maintenance commands that persist state.
Opening a current-schema SQLite cache may check schema compatibility, but it must not acquire the migration writer lock just to prove that no migration is required. This keeps parallel CLI/MCP reads from failing before they reach SQLite. SQLite WAL mode remains the storage-level concurrency mechanism for readers while a logical writer lease exists.
When a real writer conflict remains, the caller should receive a typed cache-busy diagnostic (cache_busy or cache_lock_contention, depending on the surface) with holder metadata when available, not a generic internal_error.
gitcode-mcp sync uses the live GitCode provider by default for a configured repository and uses the current cache as the durable local source for later reads. gitcode-mcp sync --offline or gitcode-mcp sync --fixture selects the deterministic fixture/offline provider for docs smoke and tests.
Large collection sync can run through the local service job queue:
gitcode-mcp sync --repo YOUR_OWNER/YOUR_REPO --issues --pulls --pr-comments --daemon
gitcode-mcp sync --repo YOUR_OWNER/YOUR_REPO --issues --pulls --pr-comments --detach
gitcode-mcp service jobs
gitcode-mcp service attach JOB_ID
gitcode-mcp service cancel JOB_ID--daemon starts a service-owned sync job and keeps the CLI attached to compact sync progress. --detach starts the same service-owned job and returns the job id immediately. The daemon path is collection-oriented; targeted --id/--input sync remains a foreground operation. Jobs use the same bulk sync service paths as foreground sync, so existing frontiers/checkpoints still drive resumability after bounded or interrupted collection sync.
Service job state is stored separately from the cache in the service runtime jobs.json snapshot. It is operational state, not cache content: active jobs are always kept, completed/cancelled/failed/interrupted history is pruned to the latest 128 terminal jobs, and each job keeps only the latest 256 progress events. This keeps long-running sync/RAG observability bounded without deleting cached GitCode records.
The sync command supports these live sync selectors:
--repo REPOselects the configured repository binding.--issuesbulk-syncs primary issue records and durably enqueues secondary comment coverage without calling per-issue comment endpoints.--wikibulk-syncs wiki records.--pullsbulk-syncs pull request records.--issue-commentsdrains the durable issue-comment queue independently of parent issue traversal. With a complete issue frontier it uses the repository-wide comment collection; otherwise it keeps the per-issue compatibility path. Combine it with--issuesto run parent backfill first and then drain comments.--pr-commentsbulk-syncs pull request comments and review metadata for cached pull request records.--commentsis a compatibility selector for--pr-commentsin bulk sync; with--input issue:N, it routes to the issue sync graph instead of pull request comments.--id IDand--input ALIASsync one stable record or remote alias.--indexbuilds the local index after sync.--idempotency-key KEYsupplies a deterministic sync event key.--max-pages,--max-records, and--per-pagebound collection sync when the selected surface supports collection bounds. If no max bound is supplied, collection sync traverses untilend_of_collectionor a complete frontier watermark proves the remaining tail is already cache-covered.
Bulk sync treats issues, wiki pages, pull requests, and pull request comments as bounded collections. Issue and pull request sync page through list APIs and commit each returned record independently. Bounded issue and pull request sync request recent-update descending order and record collection frontier metadata in sync_frontiers. A bounded, timed-out, or partially failed run only proves that the current invocation traversed a slice; it must not poison later traversal by causing early-stop before older records are backfilled. A later run can use a high-watermark stop condition only when the previous frontier for the same repo, surface, ordering, and filter scope is complete. Issue collection sync is parent-first: it persists list-provided issue records and updates issue_comment_sync, but never calls a per-issue comment endpoint. With a complete parent frontier, issue-comment sync pages through the repository-wide collection, upserts each page idempotently, reconciles every comment through both provider issue id and issue number, and marks parent queue items complete only after reaching the collection tail. A bounded or interrupted aggregate run leaves the queue pending and restarts from page 1; this conservative replay avoids page-number drift and is cheap because page upserts are idempotent. Only a successful full pass removes stale cached comments. Unknown or conflicting parents are explicit retryable reconciliation failures. If the aggregate route is unavailable, or if the parent frontier is incomplete, the service retains the per-issue compatibility path. Wiki sync passes record bounds into the wiki provider traversal before committing individual pages, then uses list-level wiki revision metadata before deciding whether a page body fetch is necessary. Pull request comment sync walks cached pull request records and applies record bounds across the resulting comment records. PR review metadata from the comment payload is stored separately from the searchable comment body so cached reads can group review discussions without live network access. Schema version 13 stores review discussion rows and per-comment diff positions so inline review comments can be matched to changed paths and lines using GitCode position metadata. Schema version 14 stores issue and pull request collection frontier metadata. Schema version 16 adds the durable issue-comment queue. Live adapter route construction stays behind the provider boundary, and operator docs should use sanitized placeholders rather than real repository coordinates.
Parent restart behavior is intentionally conservative because the public issue API is page/per-page, not cursor based. If a bounded run cached 5,000 issues while 10,000 remain, the next --issues run starts at page 1 because the previous frontier is not complete; resuming directly at a stored page number could skip records when newer issues shift page boundaries. Already cached parent revisions take the cheap skipped_by_revision path, do not trigger comment reads, and do not consume the next run's --max-records/--max-pages coverage budget. The run can therefore scan through the known 5,000 and spend its bound on the missing tail. pages_listed and records_listed still report actual list traffic, while the per-record skipped_by_revision counter explains the rescan. Comment work remains durable and independent in issue_comment_sync; --issue-comments drains pending/deferred items later and can be bounded with --max-records or --max-pages. Repository aggregate progress reports aggregate_requests, comments_listed, reconciliation failures, and parent_requests_avoided. A rate limit defers aggregate traversal without fanning out into per-issue requests; the next foreground or daemon run safely replays from page 1.
The command context carries the configured default_timeout, including the --timeout override, so large collection syncs have a whole-operation deadline in addition to provider-level request timeouts. When the deadline or caller cancellation fires, completed resource commits remain visible in cache and the sync response reports partial counts plus a typed diagnostic such as sync_timeout or sync_cancelled.
Labels and milestones are not yet exposed as bulk sync service surfaces. The milestones CLI command and list_milestones MCP tool perform a live list read and refresh cached milestone records, but they do not yet maintain collection frontiers. When milestone bulk sync is added, it should use the same SyncBounds and partial-result contract.
Bulk collection responses also expose traversal metadata when available:
pages_listedandrecords_listedcount list-page work done before staging/filtering;skipped_by_watermarkcounts list records skipped because a previouscompletefrontier proves that the remaining tail is already cache-covered;orderingreports the server-side ordering contract, currentlyupdated_at_descfor bounded issues and pull requests;stop_reasonreports why traversal stopped, such aswatermark,end_of_collection,max_pages, ormax_records;traversal_statusclassifies the run ascomplete,bounded,timeout,cancelled, orpartial;watermark_statusandwatermark_reasonexplain whether early-stop was disabled, eligible, or used. Bounded or partial previous frontiers disable early-stop; complete frontiers make it eligible.
Each successful resource sync records a SyncEvent with:
started_atandcompleted_attimestamps;remote_revisionwhen the provider exposes one;- count metadata for fetched, inserted, updated, skipped, and conflict totals;
- collection metadata counts when available:
listed,fetched_detail,skipped_by_revision, andfailed; zero_deltawhen a re-sync fetched records but all fetched content was unchanged.
Re-syncing unchanged content records a zero-delta event instead of duplicating cached records. CLI bulk sync output is compact by default: stdout reports aggregate success/failure counts, summed sync counters, grouped failure counts, elapsed time, and timeout/cancellation diagnostics, while stderr carries progress lines for the current collection/page and committed record count. Per-resource sync evidence remains available with --details or --records. gitcode-mcp sync_status reports cache freshness from the stored source records and latest completed sync events; aggregate sync-status --format json also defaults to a compact summary and uses --details or --records for the full per-record results[] payload.
Collection sync should do cheap list work before expensive detail or body fetches. Bounds are applied to list candidates first, then the sync engine uses collection-specific metadata to decide whether each bounded candidate needs a detail/body request.
Current collection behavior:
| Collection | List-level marker | Current sync strategy |
|---|---|---|
| Wiki pages | sha/revision from wiki contents/list entries |
Cache-aware. If cached remote_revision matches the list marker, cached source content exists, and status is fresh, sync records a zero-delta skipped_by_revision result without fetching the page body. New, changed, incomplete, or marker-less records fetch the full page body. |
| Issues | updated_at, comments, stable id, numeric number, and the list-provided source content |
Parent-first. Bulk issue sync stages issue content from the list payload, never performs per-issue comment reads, and enqueues comment coverage only when the list marker indicates comments may need refresh. Matching parent revisions use skipped_by_revision while pending/deferred child coverage remains independently visible. |
| Pull requests / merge requests | updated_at, stable id, numeric number, branches/diff refs, labels, and list-provided source content |
Bulk pull request sync stages from the list payload and does not perform per-PR detail fetches in the current path. The stored remote_revision is the list-version token so future detail expansion can compare before adding detail calls. |
| Pull request review comments | The v5 list exposes root comments with nested reply records. The read-only v4 discussion route can expose either nested discussions or a flat data envelope containing timeline notes and diff positions; v4 reply notes do not reliably carry parent ids. |
The adapter flattens v5 nesting to stable parent ids, accepts both observed v4 response shapes, and merges v4 position metadata by comment id. The v5 graph is canonical RAG text; flat v4 rows are retained only when their discussion has inline evidence, so unrelated timeline events do not become chunks. Reply writes refresh the same graph only after v5 list readback. The parent PR comment list call is still required because there is no persisted parent comment-collection checkpoint. |
| Issue comments | Issue list updated_at plus comments count; the repository aggregate exposes comment updated_at and target.issue.{id,number}. |
--issues creates or refreshes durable queue items. Once the parent frontier is complete, --issue-comments scans the aggregate route, commits pages idempotently, and reconciles cached parents only after a complete pass. Bounds/interruption leave work pending for page-1 replay. Missing aggregate support or an incomplete parent frontier uses the per-issue fallback. Targeted --input issue:N remains an immediate single-record refresh path. |
| Labels | No reliable update marker documented for this cache surface | Not a first-class bulk sync collection yet; use full refresh or a future invalidation strategy. |
| Milestones | Model supports updated_at, but list behavior and cache surface need verification |
Not a first-class bulk sync collection yet; do not claim metadata skip until live discovery confirms the marker and persistence contract. |
The compatibility counters keep their older meaning: fetched counts one processed remote candidate and skipped counts unchanged work. Metadata-first sync adds listed, fetched_detail, and skipped_by_revision so callers can distinguish "listed and skipped without body fetch" from "fetched detail and found no content delta."
For large repositories, combine revision metadata with server-side ordering. Bounded issue sync uses state=all&order_by=updated_at&sort=desc; bounded pull request sync uses state=all&order_by=updated_at&direction=desc. Routine refreshes list the newest changed records first. Cached records whose list revision still matches skip the detail phase, such as issue comments. If the prior sync_frontiers row is complete, traversal can stop after the listing falls below that complete high-watermark because the remaining tail is already cache-covered. If the prior frontier is bounded, timed out, or partial, traversal keeps listing past cached rows so older missing records can be backfilled. Full refresh and repair workflows should still be available by using explicit bounds or future full-refresh flags when the operator needs to walk the whole collection.
PR comment collection has no verified repository-wide endpoint. Its safe inclusion policy is therefore explicit and parent-scoped: --pr-comments is never implied by --pulls, candidates come only from already cached PRs, and large or daemon jobs should supply --max-records/--max-pages. A durable per-PR queue should use (repo_id, source_id) with the list-provided notes marker, pending|deferred|complete state, attempts, retry-after, last counts, and update time; a rate limit defers the current parent and stops the drain. Until that queue is implemented, interruption restarts the bounded cached-parent walk and idempotent record replacement prevents duplicates. Do not schedule an unbounded full-body PR crawl by default.
RAG sizing must be based on fetched user comment/reply bodies, not a fixed multiplier per PR and not the v4 timeline count. The 2026-07-13 bounded sample described in gitcode-api-discovery.md observed 1.00-1.27 size-based 4 KiB chunks per v5 note across five repository strata (1.11 overall). Use that interval only as a conditional planning bound after measuring the selected PRs' v5 note count; it is not a corpus prevalence estimate, and larger unseen bodies leave the theoretical upper bound open.
gitcode-mcp cache reset --live --repo REPO is the supported current-schema live repair command. It clears only repo-scoped live GitCode cache surfaces: remote/live records, their searchable source projections, review discussion/position metadata, and sync_frontiers for the selected repository. It does not delete the SQLite cache file, repository bindings, fixture/local records for other repositories, global cache directories, or repo-local cache directories outside the selected cache path.
Resetting live cache data also clears the collection frontiers used by watermark early-stop. The next issue or pull request sync therefore cannot trust an old complete frontier and must list from the newest page again. Unchanged records can still skip detail work after they are listed and their revision metadata matches; missing older tail records are backfilled by continuing traversal until the new run reaches its bounds or records a fresh complete frontier. If the reset is part of a repair, run sync without --max-pages/--max-records to walk to the collection tail, or set explicit bounds large enough to cover the suspected gap.
A bounded or cancelled run records a non-complete frontier, so it is not eligible for watermark early-stop. A later unbounded collection sync starts at the newest page, re-lists already cached records cheaply by revision, continues past the previous bounded frontier, and fills the missing tail before recording a new complete frontier.
Bulk sync treats each listed issue or wiki page as an independent resource. A failure for one resource does not roll back resources that already synced successfully and does not prevent later resources from being attempted.
When any resource fails, the service returns PartialSyncError with:
success_countfor resources committed successfully;failure_countfor resources that failed;- per-resource details including source id or remote alias, remote type, diagnostic message,
failure_class, endpoint, HTTP status when known, and remediation hint when available.
Successful parent resources remain committed to the cache. Child-resource failures are grouped by remote type, diagnostic class, endpoint pattern, and status code in compact summaries so an operator can distinguish repeated route compatibility failures from isolated record failures. Failed resources are reported to the caller and can be retried with the same repository, selector, and idempotency key strategy.
Issue comment reads are secondary to primary issue collection sync. Parent traversal commits the issue as fresh and records comment coverage independently as pending, deferred, or complete in issue_comment_sync. An aggregate --issue-comments traversal that receives rate_limited stops without per-issue fan-out and leaves the queue retryable. The compatibility path marks the current per-issue item deferred before stopping. Later drains or targeted --input issue:N refreshes can retry comment coverage without changing or blocking the parent issue frontier. Reconciliation failures use issue_comment_reconciliation and keep affected coverage pending. sync-status reports both the primary source state and this secondary queue state. This keeps large repository backfills useful for search and RAG even when child comment endpoints are temporarily too expensive.
Actionable failure classes include:
- authentication or authorization failures from the live provider;
- rate-limit responses;
- network, timeout, and context-cancellation failures;
- partial or oversized provider responses;
- missing remote resources;
- cache integrity, write, or lock failures.
The CLI renders the aggregate success and failure counts and resource details. Diagnostics must stay public-safe: tokens, Authorization headers, cookies, private repository coordinates, and raw API bodies are not printed.
RAG state is additive to the GitCode cache. The canonical source records and index chunks remain readable when no embedding provider is configured. Embeddings live in chunk_embeddings, keyed by (repo_id, namespace_id, chunk_id), so deleting or rebuilding RAG coverage does not invalidate records, comments, sync events, snapshots, or chunk text.
An embedding_namespaces row captures provider/model identity: provider id and type, model id and revision, dimension, dtype, normalization, document/query instruction ids, chunk policy id, language policy id, and config hash. A provider, model, dimension, instruction, chunking, or language-policy change therefore creates a different namespace. The old namespace can be retained or removed without touching the GitCode cache.
The older chunks.embedding column is a legacy placeholder. It is nullable and not namespace-aware, so new RAG indexing must not treat it as canonical coverage. It remains in place for compatibility with existing schemas and read paths.
rag_index_runs stores long-running indexer progress: namespace, profile, status, total/embedded/skipped/failed counts, timestamps, error class, message, and small metadata. CLI/MCP progress surfaces can read this without contacting the embedding provider.
The implemented cache schema version is 16, matching currentSchemaVersion in internal/cache/schema.go.
The primary version source is the SQLite schema_version table. Migrations also update PRAGMA user_version as an additive SQLite diagnostic bridge, but cache compatibility decisions use schema_version.
Compatibility policy:
| Detected version | Behavior | Operator action |
|---|---|---|
| New empty cache | Initialize normally at schema version 16 | None |
| 16 | Open normally; reads and writes are allowed | None |
| 2-15 | Open read-compatible but writes are blocked until migration | Run gitcode-mcp migrate-cache --confirm |
| 1 | Block migration as pre-supported/iteration-1-equivalent | Confirm the selected cache path, move aside or delete only that cache file, then re-sync |
0, missing, or empty schema_version in a non-empty cache |
Block as pre-schema-versioning or unknown | Confirm the selected cache path, move aside or delete only that cache file, then re-sync |
| Greater than 16 | Block as newer than this binary supports | Upgrade gitcode-mcp to a binary that supports the schema |
gitcode-mcp migrate-cache --confirm runs supported older-version migrations in place from the selected effective cache path, including repo-local cache selection when run from a repo-local workspace. Explicit --cache-path still overrides repo-local discovery for emergency repair. The command creates a backup at {cache-path}.backup-{timestamp} before applying changes. Each migration step runs in a transaction and advances both schema_version and PRAGMA user_version only after that step succeeds.
Schema version 13 adds pr_review_discussions and pr_review_positions. The migration creates empty tables and does not invent position metadata for comments already cached under older schemas. A later pull request comment sync, add-pr-review-comment, or reply-pr-review-comment write refreshes the affected comment rows and position tables. PR comment content_hash includes position metadata, so a resync can update stale rows when the adapter merges richer v4 discussion data with v5 parent/reply relationships.
Schema version 14 adds sync_frontiers for issue and pull request collection traversal metadata. Existing caches start with no complete frontier rows after migration, so the first post-migration bounded sync will not early-stop from legacy record timestamps. A run that reaches end_of_collection or safely stops via a previous complete frontier records status=complete; bounded, timed-out, cancelled, and partial runs record non-complete statuses that are not eligible for early-stop.
Schema version 15 adds RAG namespace, chunk embedding, and index run tables. Existing records, chunks, snapshots, and sync frontiers are not rewritten. Existing chunks.embedding data, if present, is left untouched and treated as non-canonical legacy data.
Schema version 16 adds issue_comment_sync, a per-issue durable work queue keyed by repository and stable source id. Existing issue records are seeded lazily when an operator first runs --issue-comments; new parent backfills populate the queue as each issue is committed.
Opening an older compatible cache without migration is read-compatible but write-blocked so operators can inspect the cache and run diagnostics before applying the migration. New caches are initialized directly at the current schema version.