Codebase Embeddings: Refresh Pipelines When Git SHAs Keep Moving

Codebase Embeddings: Refresh Pipelines When Git SHAs Keep Moving

Nightly full reindexes are late and expensive. On every merge, detect changed paths, rechunk symbols, upsert vectors keyed by (path, chunk_id, commit), and tombstone deletes so code RAG stays within minutes of main without burning a Bedrock Titan/Cohere budget every night.

⚡ TL;DR: Diff prev_sha...HEAD; re-embed only changed/added files; delete vectors for removed paths; store commit metadata for provenance; run on CodeBuild/Batch triggered by merge. Measure staleness (p95 minutes behind main) and embedding $. See OpenSearch vs pgvector and RAG evaluation.

Diff-driven worklist

# CI on merge to main
PREV=$(cat s3://embed-state/last_sha || git rev-parse HEAD~1)
CURR=$(git rev-parse HEAD)
git diff --name-status $PREV...$CURR > /tmp/changed.txt
def work_items(diff_lines: list[str]) -> tuple[list[str], list[str]]:
    upsert, delete = [], []
    for line in diff_lines:
        status, path = line[0], line[2:].strip()
        if status == "D":
            delete.append(path)
        elif status in ("A", "M", "T"):
            if path.endswith((".ts", ".tsx", ".py", ".go")):
                upsert.append(path)
        elif status.startswith("R"):
            # rename: delete old, upsert new — parse git diff -M carefully
            pass
    return upsert, delete

Chunk by symbols, not naïve 512-token windows

// Prefer function/class boundaries for code RAG
export function chunkTypeScript(source: string, path: string): Chunk[] {
  // ts-morph or tree-sitter → symbol ranges
  // ✅ each chunk: {path, symbol, startLine, endLine, text, hash}
  // ❌ sliding windows that bisect signatures
  return [];
}

Hash chunk text; skip re-embed when hash unchanged (reformats-only commits).

Upsert + tombstone

# OpenSearch example — analogous for pgvector
def upsert_chunks(chunks: list[dict], commit: str):
    for c in chunks:
        client.index(index="code", id=f"{c['path']}::{c['chunk_id']}", body={
            **c,
            "commit": commit,
            "embedding": embed(c["text"]),  # Bedrock embedding model
            "deleted": False,
        })

def tombstone(paths: list[str]):
    for p in paths:
        client.update_by_query(index="code", body={
            "query": {"term": {"path.keyword": p}},
            "script": {"source": "ctx._source.deleted = true"},
        })

Retrieval must filter deleted=false. Optionally hard-delete after N days.

State and backfill

s3://embed-state/last_sha
s3://embed-state/runs/{sha}.json  # counts, $ estimate, duration

Weekly: sample full-tree hash audit vs index to catch missed events. On disaster: full reindex job with the same chunker version — version your chunker like an API.

Illustrative cost: embedding ~1–3M tokens of changed code/day on a busy monorepo vs 80–200M for nightly full — order-of-magnitude savings.

Closing checklist

✅ Dos
– ✅ Merge-triggered incremental embed from git diff
– ✅ Symbol-aware chunking with content hashes
– ✅ Tombestone deletes; filter at query time
– ✅ Persist last_sha + per-run metrics on S3
– ✅ Audit weekly; version the chunker

❌ Don’ts
– ❌ Don’t rely only on nightly full reindex for eng chat
– ❌ Don’t leave deleted files retrievable
– ❌ Don’t re-embed unchanged hashes
– ❌ Don’t forget renames (delete old path keys)
– ❌ Don’t mix chunker versions in one index without namespaces

Related reading

Last updated on September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

1 Comment

Leave a Reply