Skip to content

The code-graphs cache grows without bound and is never pruned when a worktree is removed #711

Open

Description

@cdeust

Summary

~/.cache/cortex/code-graphs grows without bound. Every project or worktree where a session opens gets its own code graph of 0.6 to 1.1 GB, and nothing removes it when the worktree is deleted. A machine used with many worktrees accumulates tens of gigabytes.

Evidence

Measured on 2026-10-10 on a development machine that runs many short-lived worktrees.

  • mcp_server/handlers/ingest_codebase.py line 108 builds the location as ~/.cache/cortex/code-graphs/<project key> and the SessionStart hook ingests the current project in a detached background process.
  • The directory held 60 entries and 12 GB. Entries are named after worktrees (for example fix-665-windows-registry-44d852f6) with 0.6 to 1.1 GB each. Nine of them recorded a project root (in meta.json) that no longer existed, 3.4 GB; 43 had no meta.json at all (5.2 GB), so they cannot be tied to a root or cleaned safely.
  • Same ingestion process whose leak in the test suite was fixed by PR test: stop the hook tests leaking detached workers and fail the run if any process outlives it #679.

Expected

A graph directory always carries a meta.json with its root; a cache is removed when its root no longer exists (a cleanup at SessionStart and when a worktree is removed), the total size is bounded with an eviction rule that never touches a graph in use, and a worktree session does not rebuild a full graph when the parent project's graph is current (share it, or ingest incrementally). A test that deletes a root and expects its graph to be pruned, and one that a directory without meta.json is reported, not silently kept.

Activity

  1. cdeust commented on Nov 4, 2026

    @cdeust
    OwnerAuthor

    First slice opened: #869 (Refs #711, does not close the issue).

    • Cause: nothing tied a graph directory to its project. The only cleanup matched siblings by name prefix. So it never removed the graph of a deleted worktree, and it deleted unrelated projects (cortex-viz-* when cortex was ingested).
    • Slice 1 (fix(ingest): tie each code graph to its root and prune graphs of deleted roots (#711, slice 1) #869): Cortex writes cortex-root.json next to each graph it builds. It removes graphs whose root no longer exists. A directory with no marker is reported in the log and kept.
    • Remaining slices: (2) a size cap with an eviction that never touches a graph in use; (3) pruning when a worktree is removed; (4) a worktree reuses the parent's graph or ingests it incrementally.
    • Open question for the owner: the ~43 existing directories with no marker. Tying them to a root would mean reading AP's meta.json, whose schema is not defined in this repository.

    Generated by Claude Code

  2. cdeust commented on Nov 5, 2026

    @cdeust
    OwnerAuthor

    Second slice opened: #897 (Refs #711, does not close the issue).

    • Cause: slice 1 pruned orphaned graphs only at the end of a successful ingest_codebase. A session with a fresh graph, no pipeline or a failed analyze never pruned.
    • Slice 2 (fix(hooks): SessionStart prunes orphaned code graphs even when it ingests nothing (#711, slice 2) #897): SessionStart reads the cortex-root.json markers of ~/.cache/cortex/code-graphs and, when one names a root that no longer exists, starts a detached worker (code_graph_prune_background) that removes it. Unmarked directories are still reported and kept.
    • Remaining slices: (3) a size cap with an eviction that never touches a graph in use (needs a sourced cap value: owner decision); (4) pruning when a worktree is removed; (5) a worktree reuses the parent's graph or ingests incrementally.
    • Not verified: the hook's added latency on a real ~60-entry cache, and the CI mutation gate.

    Generated by Claude Code

  3. cdeust commented on Nov 6, 2026

    @cdeust
    OwnerAuthor

    Passage autopilot : aucune tranche restante n'est livrable sans décision du propriétaire, je pose autopilot:hold.

    • Tranche 3 (plafond de taille + éviction) : aucune valeur de plafond n'est sourcée dans le dépôt (règle « No source, no implementation »). À trancher : le plafond (en Go, ou en nombre de graphes) et la règle d'éviction (LRU sur la date d'accès ? jamais un graphe dont le cortex-root.json nomme un répertoire existant ?).
    • Tranche 4 (élagage à la suppression d'un worktree) : depuis fix(hooks): SessionStart prunes orphaned code graphs even when it ingests nothing (#711, slice 2) #897, le SessionStart suivant supprime déjà le graphe d'un worktree disparu. Un élagage au moment de la suppression demande une source d'événement : git n'a pas de hook post-worktree remove, et le dépôt ne référence aucun événement de suppression de worktree côté hôte. À trancher : le SessionStart suffit-il (fermer cette tranche), ou faut-il brancher un événement hôte précis (lequel) ?
    • Tranche 5 (worktree réutilisant le graphe du parent) : choix de conception entre partager le graphe du projet parent (lecture seule, risque de résultats périmés pour les fichiers modifiés dans le worktree) et une ingestion incrémentale depuis le graphe parent. À trancher : lequel.

    Generated by Claude Code

  4. added
    autopilot:holdCI autopilot stood down: a human decision is required
    on Nov 6, 2026
  5. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    Décision : cette issue n'attend plus de réponse du propriétaire.

    Le propriétaire a demandé le 12 novembre 2026 que les issues avancent sans lui ; il ne répondra pas aux questions posées ici. Ce commentaire de cdeust lève le hold et précise la demande. Il vaut pour toute la suite de cette issue.

    • Tranche toi-même chaque question restée ouverte : d'abord une décision écrite et acceptée du dépôt (ADR accepté, docs/), puis ce que le texte de l'issue propose ou exclut, puis les standards du dépôt ; à défaut, la plus petite tranche qui réalise ce que l'issue demande. Une valeur qui manque se mesure, elle ne s'invente pas.
    • Écris la décision dans un commentaire marqué <!-- ci-autopilot decision --> (ce qui est retenu, la raison et sa source, ce qui est écarté, comment revenir en arrière), puis livre la tranche dans une PR. Une décision écrite ne se rejoue pas aux passages suivants.
    • Ne repose pas autopilot:hold pour une question : seulement pour l'irréversible, un accès que tu n'as pas, une dépendance nommée encore ouverte ou le plafond de tentatives, en nommant exactement ce qui manque.
    • Jamais de tag, de release, de montée de version du projet ni de déclenchement manuel d'un workflow.

    Règle complète : #343 (comment)

    Écrit par la session « écosystème autonome » sur instruction du propriétaire (12 novembre 2026).


    Generated by Claude Code

  6. removed
    autopilot:holdCI autopilot stood down: a human decision is required
    on Nov 12, 2026
  7. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    Hold autopilot:hold levé : un commentaire de cdeust (« Décision : cette issue n'attend plus de réponse du propriétaire », 12/11/2026) a été posté après la pose du hold.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions