Abdullah Kassha · blog

The Hidden Second Job of `.git` in Codex: Repository Metadata vs. Project Boundary

An empty directory named .git produced an odd split-screen result in a local Codex workspace:

Git CLI: not a Git repository
Codex behavior: instruction discovery changed

How can a directory that Git rejects still matter to Codex?

The answer is that .git can play two conceptually different roles in this workflow. Git uses it as repository metadata. Codex can use its presence as a filesystem marker that defines a project root. Those roles usually point to the same directory, so the distinction stays hidden. An empty .git exposes it.

This article develops that mental model from four evidence layers: current documentation, current source code, a controlled boundary experiment, and issue reports. The model is a synthesis, not an official Codex term.

Verification note (August 22, 2026): The documentation, implementation files, and issue statuses linked below were re-checked before drafting. The controlled experiment itself was performed on August 20–21, 2026; its exact Codex desktop build was not captured.

The mental model: one name, two questions

Git and Codex do not need the same answer from a path named .git.

Two meanings of .git The name .git branches into Git repository metadata and a Codex project-root marker. The two roles normally align but are not semantically identical. One name, two questions .git Git interpretation Repository metadata HEAD · objects · refs config · index · hooks Codex interpretation Project-root marker Discovery boundary for project instructions Normally aligned ≠ semantically identical

Git asks whether the repository metadata is usable. The git init documentation describes an initialized repository as a .git directory with structures such as objects and refs, plus template files. A merely empty directory with the same name does not supply that repository structure.

Codex project-root discovery asks a different question: has it reached a directory containing a configured root marker? The current advanced configuration documentation says that Codex walks upward from the working directory and, by default, treats a directory containing .git as the project root. The marker list is configurable through project_root_markers; .git is important here because it is the default, not because it is the only possible marker.

That gives us the compact version of the model:

Git cares whether .git resolves to usable repository metadata. Codex root discovery can care that a configured .git marker exists.

This is deliberately an explanatory simplification. It describes the default marker behavior verified for this investigation, not every possible configuration or future release.

Four terms that make the rest predictable

Before following the boundary, it helps to separate four terms:

  • Working directory: the directory in which the Codex task starts.
  • Project root: the filesystem boundary Codex selects for project-scoped discovery.
  • Root marker: a name such as .git whose presence can identify that boundary.
  • AGENTS.md: a file that supplies project guidance to Codex for the applicable part of a directory tree.

Here, “project root” means the filesystem discovery boundary. It does not mean an application-level Project used to organize chats and folders in the Codex interface.

How the project root bounds AGENTS.md

The current AGENTS.md documentation describes a path algorithm, not a global search:

  1. Codex starts with the discovered project root.
  2. It walks down the ancestry path toward the current working directory.
  3. In each directory on that path, it selects at most one applicable instruction file.
  4. It combines those files root-first, so nearer guidance appears later and can specialize or override broader guidance.
  5. If no project root is found, it checks only the current directory.

Consider this tree:

shop\
├── .git ← default project-root marker
├── AGENTS.md ← project-wide guidance
├── frontend\
│ ├── AGENTS.md ← frontend guidance
│ └── components\ ← working directory
│ └── Button.tsx
└── backend\
└── AGENTS.md ← sibling branch

For a task started in frontend\components, the relevant directory chain is:

shop
frontend
components

shop\AGENTS.md and shop\frontend\AGENTS.md are on that path. The backend file is not. Saying that “AGENTS.md inheritance follows ancestors, not arbitrary siblings” is a useful deduction from the documented algorithm; it is not quoted product terminology.

The root matters because it defines where that ancestry begins. Move the root closer to the working directory and guidance above it falls outside the discovery path.

The edge case: .git exists, but there is no repository

Now place an empty .git at the working-directory level:

my-app\
├── AGENTS.md
└── src\
└── feature\ ← working directory
└── .git\ ← empty

Git can reject feature as a repository because the expected metadata is absent. Under the default Codex root-marker behavior, the same filesystem entry can identify feature as the nearest project root. my-app\AGENTS.md then sits above the boundary.

This is not just an interpretation of the prose documentation. The Codex implementation re-checked for this draft describes discovery as walking upward until a configured marker entry is found, using .git as the default when no custom list is set. The corresponding tests explicitly note that .git may be a file or a directory. See the pinned agents_md.rs snapshot and the current agents_md_tests.rs.

Issue #25651 provides an independent real-world example. Its reporter found that an empty nested .git prevented repository-root guidance from loading. A Codex maintainer explained that root discovery uses filesystem markers and does not validate the entry as a functional Git repository. The issue is now closed as “not planned,” treating that marker behavior as expected rather than as a loader defect.

There is one current implementation caveat worth keeping separate: project trust can also gate whether project-scoped instructions load at all. That is an independent decision from where the marker-based root is found, and it was not the variable changed in the experiment below.

A controlled project-boundary experiment

The strongest original evidence came from a four-phase experiment on Windows using PowerShell and a fresh Codex thread for every phase. Fresh threads mattered because an existing thread could retain prior instructions and blur the result.

The sanitized test tree was:

workspace\
└── main-threads-lab\
└── discovery-probe\
├── AGENTS.md
└── child\ ← working directory

The parent AGENTS.md contained one exact probe rule:

When the user sends exactly:
ROOT_MARKER_PROBE
respond exactly:
PARENT-AGENTS-SEEN

An exact PARENT-AGENTS-SEEN response therefore indicated that the parent instruction had reached that fresh thread.

PhaseRoot-marker stateFresh-thread result
AEmpty higher marker existed above discovery-probe; no marker in childPARENT-AGENTS-SEEN
BAn empty child\.git was addedROOT_MARKER_PROBE acknowledged.
CThe empty child\.git was removedPARENT-AGENTS-SEEN
DThe higher marker was removed too, leaving no relevant markerROOT_MARKER_PROBE

The boundary changes can be read as a small state machine:

The empty .git boundary experiment Four phases show a parent instruction being discovered, cut off by an empty child .git, restored when that marker is removed, and absent again when no root marker remains. The project-boundary experiment A · Higher root workspace/ ├── .git [empty] └── probe/ ├── AGENTS.md └── child/ Parent instruction seen B · Nearer root probe/ ├── AGENTS.md └── child/ └── .git [empty] boundary = child Parent instruction cut off C · Remove child workspace/ ├── .git [empty] └── probe/ ├── AGENTS.md └── child/ Parent instruction returns D · No root workspace/ └── probe/ ├── AGENTS.md └── child/ scope = cwd only Current-directory fallback add remove remove higher

Phase A: an ancestor root allowed parent discovery

At the start, an old empty .git still existed higher in the sanitized workspace path. Although Git did not recognize it as a repository, it provided a root marker above discovery-probe\AGENTS.md. The probe returned PARENT-AGENTS-SEEN.

This supports the conclusion that the parent file was on the root-to-working-directory path. It does not turn the higher empty marker into valid Git metadata.

Phase B: an empty nearer marker cut the parent off

An empty directory was created at child\.git, and a forced listing confirmed that it had no contents. In a new thread, the same prompt returned ROOT_MARKER_PROBE acknowledged. rather than the required probe response.

Only the nearer marker changed. The result supports the conclusion that the empty .git changed the effective project-root and instruction-discovery boundary in the environment tested. The parent AGENTS.md now sat above that boundary.

Phase C: removing the marker restored the instruction

The empty child\.git was removed. A third fresh thread again returned PARENT-AGENTS-SEEN. The A/B/C reversal is important: it makes random variation a less plausible explanation than the controlled marker change.

Phase D: no marker meant current-directory-only discovery

Finally, the higher empty marker was removed as well. With no relevant marker in the ancestry, a fourth fresh thread returned the probe text itself rather than the parent-defined response. That result is consistent with the documented fallback: when Codex cannot determine a project root, it checks only the current directory.

Together, the four phases reveal three states:

Higher root exists → parent AGENTS.md can be on the discovery path
Closer root exists → guidance above that boundary is cut off
No root is found → discovery falls back to the working directory

What the experiment does—and does not—establish

The experiment directly observed a boundary effect in one Codex environment on August 20–21, 2026. It did not reveal internal state directly; the conclusion combines the controlled A/B/C/D changes with the documented discovery algorithm and implementation evidence.

It does not establish that every Codex version, custom marker configuration, trust state, or future implementation behaves identically. The exact desktop build was not recorded, and the model selection used in part of the investigation is not an application-version identifier.

Why the distinction became practical on Windows

The investigation began with older Windows workspaces containing this pattern:

workspace\
├── .git\ [empty]
├── .codex\ [empty]
├── .agents\ [empty]
└── project-files\

Some groups shared the same creation timestamp. In those directories, a direct audit produced the compact contradiction:

Own .git: True
Git root: (none)

Matching timestamps and repeated directory triplets strongly suggest a common automated origin, but they do not prove which code path created each historical directory. The right wording is “consistent with reported behavior,” not “proven to be created by one bug.”

The issue trail makes that connection plausible:

IssueOpenedStatus checked August 22, 2026Relevance
#29492June 22, 2026OpenReports empty .git, .codex, and .agents paths appearing during Windows sandbox handling
#32872July 13, 2026OpenReproduces an empty .git in a non-Git Windows workspace and correlates it with sandbox ACL setup
#34799July 22, 2026OpenReports an empty .git created during an ordinary edit in a TFVC workspace
#25651June 1, 2026Closed as not plannedConfirms the separate discovery consequence of a spurious empty marker

There is also counter-evidence against declaring the old creation behavior universal. A clean local project tested on August 20, 2026 created and read ordinary files without producing .git, .codex, or .agents. Current Windows sandbox source, re-checked at the win.rs snapshot, contains filtering intended to prevent missing legacy protected children from being materialized as sentinel directories.

Those facts support a careful conclusion: the historical behavior did not reproduce in the clean test, and current source contains preventative handling. They do not prove that every related path is fixed in every distributed build—especially while the cited Windows reports remain open.

The second job of .git explains why a stale artifact can still matter after its original creation mechanism stops reproducing. Preventing a new marker from appearing does not neutralize one already present on disk.

A practical debugging checklist

If Codex appears to ignore an expected AGENTS.md, inspect the boundary before rewriting the instructions:

  1. Confirm the actual working directory for the task.

  2. Map the ancestor path from that directory upward.

  3. Look for the nearest configured root marker, including an unexpected .git file or directory.

  4. Compare that boundary with the location of the expected AGENTS.md.

  5. Check whether project_root_markers has been customized or disabled.

  6. Use Git itself to test repository recognition, for example:

    Terminal window
    git rev-parse --show-toplevel
  7. Compare the reported Git root with the filesystem marker you found. Do not infer repository validity from the marker’s name, Hidden attribute, or existence alone.

  8. Start a fresh Codex run or session after changing a marker so the retest uses a newly built instruction chain rather than retained context.

Do not delete a suspicious .git merely because it looks empty. First establish whether it is real repository metadata, a worktree pointer, a submodule marker, or an unintended empty artifact. The distinction in this article is a diagnostic model, not a blanket cleanup rule.

What not to conclude

This investigation does not support any of these shortcuts:

  • Every path named .git is a usable Git repository.
  • Codex validates a root marker by running Git.
  • Every AGENTS.md anywhere under a broad workspace is active.
  • A Codex Project in the interface is the same concept as a filesystem project root.
  • The old Windows creation behavior is present in every current workflow.
  • The old Windows creation behavior is completely fixed everywhere.
  • One controlled experiment is a guarantee for all future Codex versions.

It also disproved one tempting Windows heuristic: the Hidden attribute is not a reliable way to separate real from fake .git directories. A valid repository can have a non-Hidden .git. Ask Git, and inspect the metadata when necessary.

The takeaway

In a normal repository, Git’s root and Codex’s discovery root align. That convenience hides the fact that they answer different questions.

When behavior becomes surprising, identify three things:

1. Working directory
2. Project root
3. Root markers

Then trace the AGENTS.md ancestry between the first two.

The durable mental model is simple: Git repository validity is not the same question as Codex project-root detection. A .git entry can carry discovery meaning for Codex even when Git does not recognize usable repository metadata there.

Sources and provenance

  • Documented behavior: Codex advanced configuration and custom instructions with AGENTS.md, re-checked August 22, 2026.
  • Source-code-backed behavior: pinned Codex source snapshots for agents_md.rs and Windows sandbox win.rs, plus the current agents_md_tests.rs.
  • Direct observation: the sanitized A/B/C/D boundary experiment and historical local filesystem audit described above.
  • Issue-report evidence: OpenAI Codex issues #29492, #32872, #34799, and #25651.
  • Git behavior: official git init documentation.
  • Synthesis: “the hidden second job of .git” and “inheritance follows ancestors, not arbitrary siblings” are explanatory formulations derived from the evidence above, not official product terms.