openai / openai/codex

Memory consolidation hard-requires the `git` binary (`git read-tree`) — silent failure, and macOS CLT install prompt, on machines without git

Open
#38,880 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug memory
Dominant language
Rust
Stars
125k
Forks
19.5k
PR merge metrics
PR metrics pending

Description

What issue are you seeing?

Memory consolidation's git baseline (codex-rs/memories/write) hard-requires
the external git binary for one specific step, even though every other
part of the baseline is already implemented on top of gitoxide (gix).
On a machine where git is not installed, or not resolvable on PATH
without a side effect, this step silently fails every time a memory
consolidation workspace is (re)initialized.

Concretely:

  • prepare_memory_workspace (codex-rs/memories/write/src/workspace.rs)
    calls codex_git_utils::ensure_git_baseline_repository, and
    reset_memory_workspace_baseline calls codex_git_utils::reset_git_repository.
  • Both funnel into reset_git_repository_sync in
    codex-rs/git-utils/src/baseline.rs, which:
    1. gix::init(root) — in-process, gitoxide.
    2. commit_current_tree(&repo, ...) — in-process, gitoxide (writes blobs/
      tree/commit via gix::Repository::write_object / commit_as).
    3. write_index_from_head(root)shells out:
      fn write_index_from_head(root: &Path) -> anyhow::Result<()> {
          run_git_for_status(root, ["read-tree", "--reset", "HEAD"], /*env*/ None)
              .context("write git baseline index from HEAD")
      }
      
      run_git_for_status (codex-rs/git-utils/src/operations.rs) calls
      run_git, which does Command::new("git").

That one shell-out is the only reason this baseline needs a system git
binary at all.

Impact:

  • Silent failure everywhere git is missing/unresolvable. Phase 2
    logs this as failed_prepare_workspace (on first init,
    codex-rs/memories/write/src/phase2.rs around line 73-83) and
    failed_workspace_commit (after every successful consolidation, around
    line 462) — there is no user-facing surfacing of this, memory
    consolidation just stops updating.
  • A GUI popup on macOS specifically, for any host embedding the Codex
    engine (app-server, Electron/Tauri wrappers, etc.) on a machine without
    the Xcode Command Line Tools installed. /usr/bin/git on stock macOS is
    Apple's xcode-select shim (links libxcselect.dylib); when a GUI
    process spawns it as a child, macOS raises a blocking "The 'git' command
    requires the command line developer tools. Would you like to install the
    tools now?" dialog instead of just failing fast. This exact behavior for
    GUI-spawned git child processes is documented for other Electron/GUI
    apps that shell out to git, e.g. Zettlr
    (https://github.com/Zettlr/Zettlr/issues/4709), CodeEdit
    (https://github.com/CodeEditApp/CodeEdit/issues/2138), and was reported
    for Claude Desktop (https://www.eclecticlight.co, 2026-03-17). So on a
    fresh macOS machine with no dev tools installed, simply having memories
    enabled in a GUI Codex host can pop an OS dialog on first consolidation.

Also worth flagging while reading this code (not the primary bug, but
adjacent, please correct me if I'm misreading it): ensure_git_baseline_repository
treats an existing .git/ as "usable" if gix::open succeeds and HEAD
has a tree — it does not check that the index exists/matches. So if
write_index_from_head fails (as it does whenever git is missing), the
commit from step 2 above is still written, and a subsequent call sees a
.git with a valid HEAD tree and treats it as already-initialized,
without ever retrying the index write. I haven't fully traced every caller
to confirm user-visible consequences, but it looks like a half-initialized
baseline (commit present, index absent/stale) can be silently treated as
valid on retry.

What steps can reproduce the bug?
  1. On a machine with no git binary anywhere on PATH (or, to reproduce
    without uninstalling anything: temporarily run the process with
    PATH pointed at a directory that has no git in it), enable memories
    and run any session that triggers phase-2 consolidation.
  2. Observe that reset_git_repository / ensure_git_baseline_repository
    fail — surfaced only as failed_prepare_workspace /
    failed_workspace_commit log entries in
    codex-rs/memories/write/src/phase2.rs, no user-facing error.
  3. On macOS without the Command Line Tools, instead of a hard failure you
    get the "install developer tools?" dialog described above, because
    /usr/bin/git is the xcode-select shim rather than a real git.

Root cause, with exact file paths and the call chain:

  • codex-rs/memories/write/src/workspace.rs: prepare_memory_workspace ->
    codex_git_utils::ensure_git_baseline_repository;
    reset_memory_workspace_baseline -> codex_git_utils::reset_git_repository.
  • codex-rs/git-utils/src/baseline.rs: reset_git_repository_sync calls
    gix::init + commit_current_tree (both gitoxide, in-process), then
    write_index_from_head(root), which is
    run_git_for_status(root, ["read-tree", "--reset", "HEAD"], None)
    the one line in this whole module that isn't gitoxide.
  • codex-rs/git-utils/src/operations.rs: run_git_for_status -> run_git
    -> Command::new("git").

Verified against the vendored rust-v0.147.0 copy of codex-rs and
re-confirmed against openai/codex@main (commit 9ded177 as of this
writing) — the code is identical on main.

What is the expected behavior?

Memory baseline init/reset should not require an external git binary at
all, matching the rest of codex-rs/git-utils/src/baseline.rs, which is
already fully gitoxide-based. write_index_from_head should build the
index in-process from the HEAD tree instead of shelling out.

Additional information

I have a small, tested fix ready on a branch on my fork, and would like an
invitation to open a PR per docs/contributing.md
("External contributions are by invitation only ... open an issue with your
analysis and a proposed fix; the maintainers will invite a PR if it's a
good fit").

Branch (compare against main):
https://github.com/openai/codex/compare/main...hesong12:codex:fix/memories-baseline-without-git-binary

Summary of the fix:

  • codex-rs/Cargo.toml: add the "index" feature to the gix dependency
    (gix-index was already present in Cargo.lock via other transitive
    features, so this is a zero-churn Cargo.lock change — gix-index and
    gix-lock versions are unchanged).
  • codex-rs/git-utils/src/baseline.rs: write_index_from_head now opens
    the repo, resolves HEAD's tree via Repository::head_tree_id, builds
    the index from it via Repository::index_from_tree (which already
    targets <git_dir>/index as its path), and writes it with
    gix_index::File::write. Behavior is unchanged: the index ends up
    identical to the HEAD tree, so git status in that directory is clean
    and diff_since_latest_init reports no changes, same as before.
  • codex-rs/git-utils/src/operations.rs: removed run_git_for_status,
    which had no other callers left after this change (would otherwise be
    dead code under -D warnings, per your cargo clippy --tests -- -D warnings CI step).
  • Added a regression test in codex-rs/git-utils/src/baseline.rs that runs
    reset_git_repository / diff_since_latest_init in a child process
    with PATH pointed at an empty directory (no git binary resolvable at
    all), and asserts the resulting .git/index exactly matches the HEAD
    tree. The child-process isolation is deliberate: this crate's other
    tests legitimately shell out to git for their own assertions (e.g.
    git status --porcelain), and those would spuriously fail if the "no
    git on PATH" condition were applied to the whole test process instead of
    a dedicated child.

Locally verified on this branch, from codex-rs/:

  • cargo test -p codex-git-utils — 39 passed, 0 failed (stable across
    repeated runs).
  • cargo test -p codex-memories-write — 40 passed, 0 failed (including
    workspace::tests::prepare_memory_workspace_recovers_unusable_git_dir
    and workspace::tests::reset_memory_workspace_baseline_removes_generated_diff,
    which exercise the changed code path directly).
  • cargo fmt -p codex-git-utils -p codex-memories-write -- --check — clean.
  • cargo clippy -p codex-git-utils -p codex-memories-write --all-targets -- -D warnings — clean, matching .github/workflows/rust-ci-full.yml's
    clippy invocation.

Happy to submit this as a PR if the team wants to invite it, per
docs/contributing.md.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with codex-rs/git-utils/src/baseline.rs, especially reset_git_repository_sync and write_index_from_head, then trace the callers in memories/write/src/workspace.rs. Run cargo test -p codex-git-utils and cargo test -p codex-memories-write. Done means baseline initialization and reset work with no git binary on PATH, the index matches HEAD, and the existing checks pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, rust
Domain
devtools
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.