An empty directory named .git produced an odd split-screen result in a local Codex
workspace:
Git CLI: not a Git repositoryCodex behavior: instruction discovery changedHow 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.
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
.gitresolves to usable repository metadata. Codex root discovery can care that a configured.gitmarker 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
.gitwhose 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:
- Codex starts with the discovered project root.
- It walks down the ancestry path toward the current working directory.
- In each directory on that path, it selects at most one applicable instruction file.
- It combines those files root-first, so nearer guidance appears later and can specialize or override broader guidance.
- 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 branchFor a task started in frontend\components, the relevant directory chain is:
shop↓frontend↓componentsshop\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\ ← emptyGit 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 directoryThe parent AGENTS.md contained one exact probe rule:
When the user sends exactly:
ROOT_MARKER_PROBE
respond exactly:
PARENT-AGENTS-SEENAn exact PARENT-AGENTS-SEEN response therefore indicated that the parent instruction had
reached that fresh thread.
| Phase | Root-marker state | Fresh-thread result |
|---|---|---|
| A | Empty higher marker existed above discovery-probe; no marker in child | PARENT-AGENTS-SEEN |
| B | An empty child\.git was added | ROOT_MARKER_PROBE acknowledged. |
| C | The empty child\.git was removed | PARENT-AGENTS-SEEN |
| D | The higher marker was removed too, leaving no relevant marker | ROOT_MARKER_PROBE |
The boundary changes can be read as a small state machine:
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 pathCloser root exists → guidance above that boundary is cut offNo root is found → discovery falls back to the working directoryWhat 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: TrueGit 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:
| Issue | Opened | Status checked August 22, 2026 | Relevance |
|---|---|---|---|
#29492 | June 22, 2026 | Open | Reports empty .git, .codex, and .agents paths appearing during Windows sandbox handling |
#32872 | July 13, 2026 | Open | Reproduces an empty .git in a non-Git Windows workspace and correlates it with sandbox ACL setup |
#34799 | July 22, 2026 | Open | Reports an empty .git created during an ordinary edit in a TFVC workspace |
#25651 | June 1, 2026 | Closed as not planned | Confirms 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:
-
Confirm the actual working directory for the task.
-
Map the ancestor path from that directory upward.
-
Look for the nearest configured root marker, including an unexpected
.gitfile or directory. -
Compare that boundary with the location of the expected
AGENTS.md. -
Check whether
project_root_markershas been customized or disabled. -
Use Git itself to test repository recognition, for example:
Terminal window git rev-parse --show-toplevel -
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.
-
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
.gitis a usable Git repository. - Codex validates a root marker by running Git.
- Every
AGENTS.mdanywhere 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 directory2. Project root3. Root markersThen 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.rsand Windows sandboxwin.rs, plus the currentagents_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 initdocumentation. - 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.