Max Nardit
← Artikel
8 mindev

Claude Code Memory in Codex 0.165 importieren: memories vor dem ersten Import einschalten, sonst bleibt das Profil auf v1

Die Notizen zu kopieren ist der einfache Teil. Jede Claude-Code-Datei kommt unverändert an, und was Codex beim Sessionstart lädt, ist ein vom Modell geschriebener Verweis darauf, in einer Zusammenfassung mit harter Größengrenze. Zwei standardmäßig ausgeschaltete Schalter und ein überzähliges Verzeichnis entscheiden, in welcher Memory-Pipeline diese Dateien landen.

Inhalt

Die Release Notes zu Codex CLI 0.165.0, erschienen am 27. Oktober, widmen der Änderung eine Zeile: memory v2 ist jetzt der Standard für frische Profile, und Codex kann „external-agent memories“ hineinimportieren. Externer Agent heißt hier Claude Code. Der Importer liest die Auto Memory, die Claude Code pro Projekt führt, kopiert sie in den Memory-Workspace von Codex und stellt einen Modelldurchlauf in die Warteschlange, der sie in die Zusammenfassung einarbeitet, die Codex in jede neue Session injiziert.

Was die Zeile weglässt: das Flag und die Reihenfolge. Der Import hängt an einem Feature-Flag, das ausgeschaltet und als in Entwicklung markiert ist. Bei einer Standardinstallation bietet /import Memory also gar nicht erst an. Und ob du auf v2 landest, hängt von der Konfiguration ab, unter der der erste Import läuft: Ist nur das Import-Flag an, legt der Import selbst das Verzeichnis an, das dein Profil auf v1 festhält.

Ich habe den Quellcode am Tag rust-v0.165.0 gelesen und den Import dann über codex app-server gegen ein Wegwerf-Home-Verzeichnis mit drei fingierten Claude-Code-Projekten laufen lassen. Alles Folgende stammt aus einer dieser beiden Quellen, und ich sage jeweils, aus welcher.

Kann Codex 0.165 Claude Code Memory ohne Weiteres importieren?

Nein. Die Memory-Zeile im Import-Ablauf erscheint nur, wenn das Feature external_agent_memory_import an ist, und in features/src/lib.rs steht es auf Stage::UnderDevelopment mit default_enabled: false. Memory selbst ist ein separates Feature, memories, das stabil ist, aber ebenfalls standardmäßig aus.

Im Testlauf lieferte die Erkennung auf einem sauberen Profil nur die letzten Chat-Sessions. Nachdem ich das Flag in config.toml eingetragen hatte, erschien ein Memory-Eintrag. Setz beide Features zusammen, vor jedem Import:

toml
[features]
memories = true
external_agent_memory_import = true

Schickst du ohne das Flag eine Import-Anfrage für Memory, lehnt der App-Server sie mit external agent memory import is disabled ab. In der TUI stellt /import Memory als eine einzelne Checkbox in den Home-Bereich, standardmäßig angehakt, wie alles andere, was erkannt wurde.

Welche Memory-Version nutzt Codex nach dem Import von Claude Code Memory?

Wenn memories.version nicht gesetzt ist, nimmt Codex v2 nur dann als Standard, wenn das Feature memories an ist, memories.dual_write nicht explizit false ist und ~/.codex/memories, das v1-Verzeichnis, nicht existiert. Das ist alles, was hinter „fresh profiles default to v2“ in core/src/config/mod.rs steckt. Sonst fällt es auf v1 zurück. Ein explizites memories.version gewinnt immer.

Der Importer hat seine eigene Regel: Er schreibt immer ~/.codex/memories_v2 und zusätzlich ~/.codex/memories, wenn die aufgelöste Version v1 ist oder dieses Verzeichnis schon existiert. Genau die Reihenfolge, die die meisten ausprobieren werden, geht schief. Du schaltest nur das Import-Flag ein, weil der Import genau das braucht. Mit memories aus wird die Version zu v1 aufgelöst, der Import schreibt beide Namespaces, und ~/.codex/memories existiert jetzt. Schaltest du später memories ein, sieht Codex einen v1-Workspace und lässt dich auf v1.

Genau das hat der Testlauf gezeigt. Mit nur dem Import-Flag legte der Import sowohl memories als auch memories_v2 an. Auf einem zweiten sauberen Profil mit memories = true in derselben Datei legte er nur memories_v2 an. Der Integrationstest des App-Servers bildet dieselbe Matrix ab, fünf Fälle von „new v2 user“ bis „legacy workspace“.

Eine Einstellung funktioniert anders, als ihr Name vermuten lässt. memories.dual_write = false hindert den Import nicht daran, zwei Kopien zu schreiben. Es ist das Opt-out aus dem v2-Standard und hält ein frisches Profil auf v1. Echtes Dual-Write, bei dem beim Start beide Pipelines laufen, gibt es nur mit version = "v1" und dual_write = true, und dual_write ist standardmäßig false.

version = "v2" unter [memories] legt die Start-Pipeline auf v2 fest, schaltet Memory aber nicht ein und hindert den Import auch nicht daran, zusätzlich nach ~/.codex/memories zu schreiben, wenn dieses Verzeichnis existiert. Hast du schon eins, hält der Import beide Kopien aktuell.

Was kopiert der Codex-Importer aus dem Memory-Ordner von Claude Code?

Jede Markdown-Datei unter dem memory/-Verzeichnis jedes Projekts, Byte für Byte. Claude Code legt die Auto Memory in ~/.claude/projects/<project>/memory/ ab: ein MEMORY.md-Index plus eine Themendatei pro Erinnerung, mit Frontmatter. Der Importer durchläuft diesen Baum rekursiv, nimmt reguläre Dateien, die auf .md enden, egal in welcher Groß- und Kleinschreibung, und überspringt symbolische Links, sowohl verlinkte Dateien als auch verlinkte Verzeichnisse.

Jedes Projekt landet in einem eigenen Ordner mit einer zusätzlichen Datei:

~/.codex/memories_v2/extensions/external_agent_import/
├── instructions.md
└── resources/
    └── -work-app-a/
        ├── scope.json        {"cwd":"/home/you/work/app-a"}
        ├── MEMORY.md
        ├── build.md
        └── notes/deep.md

Im Testlauf fand cmp die kopierte Themendatei identisch mit der Quelle, Frontmatter eingeschlossen, und das verschachtelte notes/deep.md behielt seinen Pfad. Ein symlinktes linked.md im selben Ordner kam nicht an. Der Ordnername ist der Projektschlüssel von Claude Code, also der escapte Pfad, den du unter ~/.claude/projects siehst. Deine MEMORY.md wird unverändert kopiert, und die Konsolidierungsanweisungen verbieten, irgendetwas unter resources/ zu bearbeiten. Nach Claude Code wird nichts zurückgeschrieben.

Welche Claude-Code-Projekte überspringt der Codex-Importer?

Jedes Projekt, für das er kein Arbeitsverzeichnis findet. Der Memory-Ordner von Claude Code hält nicht fest, zu welchem Verzeichnis er gehört. Deshalb liest der Importer die Session-Transkripte des Projekts, die neuesten zuerst, und übernimmt das cwd des ersten nutzbaren. Nutzbar heißt: Das Transkript enthält eine Konversationsnachricht mit Zeitstempel, wurde nicht durch eine Fortsetzung ersetzt und nennt ein absolutes Verzeichnis, das noch existiert. Daraus wird scope.json. Ein Projekt ohne nutzbare Session wird für einen ersten Import nicht angeboten.

Zwei ganz alltägliche Situationen führen dahin. Claude Code löscht alte Session-Transkripte nach seiner Aufbewahrungsfrist cleanupPeriodDays, behält aber das Memory-Verzeichnis. Ein Projekt, das du länger nicht geöffnet hast, kann also Memory und keine Transkripte haben. Und ein Projekt, dessen Verzeichnis du verschoben oder gelöscht hast, hat Transkripte, die ins Leere zeigen. Mein Testaufbau hatte je einen Fall davon. Keiner wurde aufgelistet, und als ich beide von Hand zur Anfrage hinzufügte, meldete das Ergebnis beide als Fehler:

selected memory was not found: -work-app-b
selected memory was not found: -work-app-c

Ein Projekt, das früher schon importiert wurde und seitdem seine Transkripte verloren hat, behält seine vorhandene Kopie. Der Importer aktualisiert sie lediglich nicht mehr.

Die Quelle ist normalerweise $HOME/.claude. Der Importer liest die eigenen Pfad-Overrides von Claude Code nicht. Hast du die Auto Memory mit autoMemoryDirectory verschoben oder betreibst Claude Code unter CLAUDE_CONFIG_DIR, schaut er dort nicht nach. Laut der Claude-Code-Doku teilen sich alle Worktrees eines Repositorys ein Memory-Verzeichnis. Der Importer ordnet es dem Checkout zu, in dem die neueste nutzbare Session lief, und das kann ein Worktree statt des Haupt-Repositorys sein.

Dieses Skript ist eine Heuristik für den cwd-Teil dieser Logik, damit du vor dem Start siehst, welche Projekte ein nutzbares Verzeichnis haben. Maßgeblich ist die Liste, die die Erkennung anbietet. Das Skript gleicht nicht mit bereits vorhandenen Kopien ab.

python
import json, os, pathlib
 
def usable_cwd(session):
    cwd, has_message = None, False
    try:
        lines = session.read_text(errors="ignore").splitlines()
    except OSError:
        return None
    for line in lines:
        try:
            rec = json.loads(line.strip())
        except ValueError:
            continue
        if not isinstance(rec, dict):
            continue
        if rec.get("type") == "continued-in" and rec.get("continuedInSessionId"):
            return None
        if cwd is None and isinstance(rec.get("cwd"), str):
            cwd = rec["cwd"]
        if isinstance(rec.get("message"), dict) and rec.get("timestamp"):
            has_message = True
    if cwd and has_message and os.path.isabs(cwd) and os.path.isdir(cwd):
        return os.path.realpath(cwd)
    return None
 
root = pathlib.Path.home() / ".claude" / "projects"
for proj in sorted(root.iterdir()) if root.is_dir() else []:
    mem = proj / "memory"
    if proj.is_symlink() or mem.is_symlink() or not mem.is_dir():
        continue
    files = [f for f in mem.rglob("*") if f.suffix.lower() == ".md"
             and f.is_file() and not f.is_symlink()]
    sessions = [s for s in proj.glob("*.jsonl") if s.is_file() and not s.is_symlink()]
    sessions.sort(key=os.path.getmtime, reverse=True)
    cwd = next((c for s in sessions if (c := usable_cwd(s))), None)
    print("CWD FOUND" if cwd and files else "NO CWD   ", len(files), proj.name, cwd or "")

Auf dem Testaufbau gab es eine CWD FOUND- und zwei NO CWD-Zeilen aus, passend zu dem, was die Erkennung anbot.

Was macht die Codex-Konsolidierung mit deiner Claude-Code-MEMORY.md?

Sie liest sie und schreibt Verweise darauf in eine separate Datei. Nach dem Kopieren stellt der Importer einen Konsolidierungsjob in die Warteschlange, einen Modelldurchlauf, der memory_summary.md schreibt. Der v2-Konsolidierungsprompt sagt, diese Datei werde zu Beginn jeder neuen Session injiziert („will be injected at the beginning of every new session“), und der Validator lehnt eine Zusammenfassung ab, die nicht mit der Zeile v1 beginnt, 10.000 Byte oder mehr hat oder eine dieser Überschriften nicht enthält:

## User Profile
## User preferences
## General Tips
## What's in Memory

Der Importer ergänzt eigene Regeln in instructions.md. Diese vier Punkte, vollständig zitiert, entscheiden, was mit einem Projekt passiert, das du von Hand gepflegt hast:

- For each project, always read its source `MEMORY.md` first when it exists.
  Use it to add or update the project's memory entry in the active pipeline's
  output files.
- Treat every other source `*.md` file as detailed supporting evidence analogous
  to a rollout summary. Do not flatten its full contents into Codex `MEMORY.md`
  or `memory_summary.md`. Keep the detail in the imported resource, add a concise
  pointer in `memory_summary.md` when useful, and read the resource progressively
  when a later task needs that topic.
- In `memory_summary.md`, represent imported project memory only as a compact
  route under `## What's in Memory`. Do not copy its contents into
  `## User Profile`, `## User preferences`, or `## General Tips`, even with a
  project-scope qualifier.
- Imported resources have no rollout `updated_at`. When no reliable source date
  exists, route them under `### Older Memory Topics`; do not invent a date or use
  the consolidation date.

Die Anweisungen verlangen vom Modell also, aus jedem Claude-Code-Projekt einen kompakten Verweis zu machen, sofern es einen für nützlich hält, und ihn unter Older Memory Topics abzulegen, solange es kein Datum findet, dem es traut. All das konkurriert um eine einzige, auf 10.000 Byte begrenzte Zusammenfassung, mit jedem anderen importierten Projekt und mit der eigenen Memory von Codex. Eine Präferenz, die du einmal in Claude Code notiert hast, wird nicht zur Codex-Präferenz, denn die Regeln verbieten, importierte Inhalte in die Profilabschnitte zu heben. Ob das Modell den modified-Zeitstempel, den Claude Code ins Frontmatter schreibt, als verlässliches Datum behandelt, bleibt dem Modell überlassen. Nichts im Code parst ihn.

Die Regel ist vertretbar: Eine Präferenz, die für ein Repository festgehalten wurde, sollte nicht global werden, nur weil sie kopiert wurde. Sie bedeutet aber, dass der Import keine Migration deiner Arbeitsweise ist. Er ist eine Bibliothek von Projektnotizen mit einem Katalogeintrag für einige davon, und die Einträge schreibt ein Modell. Ein verwandtes Argument habe ich in Agent Memory als Markdown-Dokumente vertreten: Der Index ist die Retrieval-Schicht. Hier überlebt dein Index auf der Platte, und der, den Codex tatsächlich lädt, ist die Zusammenfassung von jemand anderem.

Die Konsolidierung läuft in der Memory-Start-Pipeline, die vorzeitig abbricht, wenn das Feature memories aus ist. Mit nur dem Import-Flag kommen die Dateien an, und nichts liest sie. Ich habe die Konsolidierung nicht laufen lassen und kann daher nicht zeigen, wie die Verweise aussehen. Dieser Teil beruht auf dem Prompt und dem Validator, nicht auf Beobachtung.

Was passiert, wenn du den Codex-Memory-Import erneut ausführst?

Er synchronisiert die Projekte, die du auswählst. Für jedes davon löscht der Importer den Projektordner unter resources/ und schreibt ihn neu aus der Quelle. Eine Datei, die du in Claude Code entfernt hast, verschwindet also auch aus Codex. Im Testlauf entfernte das Löschen von build.md aus dem fingierten Projekt und ein erneuter Import die Datei aus beiden Namespaces. Die Erkennung prüft auch die Kopien, die Codex schon hat: Ein kopiertes Projekt, das in Claude Code nicht mehr existiert, wird erneut angeboten, und der Import löscht dann die Kopie. Ein Projekt, dessen Kopie bereits identisch ist, wird gar nicht angeboten.

Der Sync betrifft nur die Kopien. Was Codex in seinen eigenen Sessions lernt, liegt getrennt davon in Rollout-Zusammenfassungen und wird nie nach Claude Code zurückgeschrieben. Jeder Import stellt eine neue Konsolidierung in die Warteschlange, die die Änderungen der Quelle in die gemeinsame Zusammenfassung übernimmt und Aussagen verwirft, die nur eine gelöschte Datei gestützt hat. Die beiden Memories eines Projekts driften trotzdem ab der ersten Codex-Session auseinander. Wenn du beide Tools im selben Repository einsetzt (wie man die Arbeit aufteilt), leg fest, wessen Memory maßgeblich ist, bevor du wiederholte Importe einplanst.

Warum schlägt ein Codex-Memory-Import mit „memory workspace is busy“ fehl?

Weil eine Konsolidierung oder ein anderer Import den Workspace-Lock hält. Jeder Namespace hat daneben eine Lock-Datei, ~/.codex/memories_v2.lock und ~/.codex/memories.lock, und der Import holt sich jeden benötigten Lock, bevor er irgendetwas ändert. Ist einer belegt, bricht er ab mit:

memory workspace is busy; retry after consolidation or another import finishes

Zu diesem Zeitpunkt wurde noch nichts geschrieben, es gibt also keine halb importierte v1-Kopie aufzuräumen. Versuch es erneut, sobald die Konsolidierung fertig ist. Pull Request #56462 hat die Race Condition zwischen Import und Konsolidierung geschlossen.

Was du vor dem Import von Claude Code Memory in Codex prüfen solltest

Probier es zuerst auf einem Testprofil aus. Lass CODEX_HOME auf ein leeres Verzeichnis zeigen, trag beide Features in die config.toml dieses Verzeichnisses ein und starte codex aus einem Wegwerf-Arbeitsverzeichnis, denn andere Import-Einträge können Dateien zum aktuellen Projekt hinzufügen. Wähl in /import die Option Customize selection und lass nur Memory angehakt. Schau dir dann an, was in memories_v2/extensions/external_agent_import/resources/ landet, bevor du dasselbe auf deinem echten Profil machst. Der Import liest ~/.claude und verändert es nicht.

Auf dem echten Profil prüfst du drei Dinge:

  1. Trag memories = true und das Import-Flag vor dem ersten Import in config.toml ein. Existiert ~/.codex/memories schon, ist automatisches v2 vom Tisch. Setz memories.version = "v2", wenn du die v2-Pipeline trotzdem willst.
  2. Führ das Skript oben aus. Jede NO CWD-Zeile ist ein Projekt, dessen Memory nicht mitkommt, meist weil seine Transkripte aufgeräumt wurden. Startest du in diesem Projekt eine Konversation mit Claude Code, entsteht ein neues Transkript mit einem existierenden cwd.
  3. Lies die vollständige Projektliste, bevor du zustimmst. Die TUI zeigt die Anzahl und nur die ersten vier Projektschlüssel, und ihre eine Checkbox importiert jedes erkannte Projekt in ein Profil und eine Zusammenfassung. scope.json und ein Satz in den Anweisungen sind alles, was die Notizen eines Projekts aus den Sessions eines anderen heraushält. Gehören einige dieser Projekte anderen Leuten oder sollen sich einfach keine Zusammenfassung teilen, nimm für sie ein separates CODEX_HOME oder die App-Server-API, die eine explizite Liste von Projektschlüsseln entgegennimmt.

Bei diesem Import bestehen die Standardwerte aus zwei ausgeschalteten Schaltern und einem Verzeichnis, das noch nicht existieren sollte.

01 / Diskussion

Hier gibt es keine Kommentarspalte. Diskussionen laufen auf X.

Max Nardit@mnardit

02 / Weitere Artikel