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

Open 💬 0 comments Opened Aug 16, 2026 by hesong12

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).

  1. write_index_from_head(root)shells out:

``rust
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.

  1. 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.

  1. 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.

View original on GitHub ↗