Ghost snapshot warns about ignored directories
Resolved 💬 1 comment Opened Nov 30, 2025 by baron Closed Dec 2, 2025
What version of Codex is running?
0.63
What subscription do you have?
ChatGPT
Which model were you using?
_No response_
What platform is your computer?
_No response_
What issue are you seeing?
As me and many others have found (see #7369 #7313 #7067), we are getting "Repository snapshot encountered large untracked directories" despite the directories correctly listed in .gitignore. I had Codex work on it to provide you with additional context.
Summary
- Large untracked warnings were firing for directories already listed in
.gitignore(e.g.,logs/,tmp/). - Fixed the warning pre-scan to exclude ignored paths while keeping ignored paths for snapshot/restore bookkeeping.
- Added a regression test to ensure ignored large directories no longer trigger the warning.
Files touched
codex-rs/utils/git/src/ghost_commits.rs
Details
- Pre-warning scan now calls
capture_existing_untracked(..., include_ignored = false), so git-ignored dirs are omitted from the “large untracked” check. - Snapshot creation and restore still run with
include_ignored = trueto preserve/remove ignored files correctly. - New test
ignored_large_directories_do_not_trigger_warningverifies ignored dirs don’t raise the warning.
Why it matters
- Users saw “Repository snapshot encountered large untracked directories” even when the dirs were already ignored; this was noisy and confusing. The warning now aligns with
.gitignore.
diff --git a/codex-rs/utils/git/src/ghost_commits.rs b/codex-rs/utils/git/src/ghost_commits.rs
index 01987bb5e..145843e18 100644
--- a/codex-rs/utils/git/src/ghost_commits.rs
+++ b/codex-rs/utils/git/src/ghost_commits.rs
@@ -146,7 +146,7 @@ pub fn capture_ghost_snapshot_report(
let repo_root = resolve_repository_root(options.repo_path)?;
let repo_prefix = repo_subdir(repo_root.as_path(), options.repo_path);
let existing_untracked =
- capture_existing_untracked(repo_root.as_path(), repo_prefix.as_deref())?;
+ capture_existing_untracked(repo_root.as_path(), repo_prefix.as_deref(), false)?;
let warning_files = existing_untracked
.files
@@ -174,7 +174,7 @@ pub fn create_ghost_commit_with_report(
let repo_prefix = repo_subdir(repo_root.as_path(), options.repo_path);
let parent = resolve_head(repo_root.as_path())?;
let existing_untracked =
- capture_existing_untracked(repo_root.as_path(), repo_prefix.as_deref())?;
+ capture_existing_untracked(repo_root.as_path(), repo_prefix.as_deref(), true)?;
let warning_files = existing_untracked
.files
@@ -277,7 +277,7 @@ pub fn restore_ghost_commit(repo_path: &Path, commit: &GhostCommit) -> Result<()
let repo_root = resolve_repository_root(repo_path)?;
let repo_prefix = repo_subdir(repo_root.as_path(), repo_path);
let current_untracked =
- capture_existing_untracked(repo_root.as_path(), repo_prefix.as_deref())?;
+ capture_existing_untracked(repo_root.as_path(), repo_prefix.as_deref(), true)?;
restore_to_commit_inner(repo_root.as_path(), repo_prefix.as_deref(), commit.id())?;
remove_new_untracked(
repo_root.as_path(),
@@ -327,21 +327,28 @@ struct UntrackedSnapshot {
dirs: Vec<PathBuf>,
}
-/// Captures the untracked and ignored entries under `repo_root`, optionally limited by `repo_prefix`.
-/// Returns the result as an `UntrackedSnapshot`.
+/// Captures the untracked entries under `repo_root`, optionally limited by `repo_prefix`.
+/// When `include_ignored` is true, ignored paths are included as well. Returns the result as an
+/// `UntrackedSnapshot`.
fn capture_existing_untracked(
repo_root: &Path,
repo_prefix: Option<&Path>,
+ include_ignored: bool,
) -> Result<UntrackedSnapshot, GitToolingError> {
// Ask git for the zero-delimited porcelain status so we can enumerate
- // every untracked or ignored path (including ones filtered by prefix).
+ // every untracked path, optionally including ignored entries (including ones filtered by
+ // prefix).
let mut args = vec![
OsString::from("status"),
OsString::from("--porcelain=2"),
OsString::from("-z"),
- OsString::from("--ignored=matching"),
OsString::from("--untracked-files=all"),
];
+ if include_ignored {
+ args.push(OsString::from("--ignored=matching"));
+ } else {
+ args.push(OsString::from("--ignored=no"));
+ }
if let Some(prefix) = repo_prefix {
args.push(OsString::from("--"));
args.push(prefix.as_os_str().to_os_string());
@@ -621,6 +628,26 @@ mod tests {
Ok(())
}
+ #[test]
+ fn ignored_large_directories_do_not_trigger_warning() -> Result<(), GitToolingError> {
+ let temp = tempfile::tempdir()?;
+ let repo = temp.path();
+ init_test_repo(repo);
+
+ std::fs::write(repo.join(".gitignore"), "logs/\n")?;
+ let logs = repo.join("logs");
+ std::fs::create_dir_all(&logs)?;
+ for idx in 0..=LARGE_UNTRACKED_WARNING_THRESHOLD {
+ let file = logs.join(format!("file-{idx}.txt"));
+ std::fs::write(file, "ignored data\n")?;
+ }
+
+ let report = capture_ghost_snapshot_report(&CreateGhostCommitOptions::new(repo))?;
+ assert!(report.large_untracked_dirs.is_empty());
+
+ Ok(())
+ }
+
#[test]
fn create_snapshot_reports_nested_large_untracked_dirs_under_tracked_parent()
-> Result<(), GitToolingError> {
What steps can reproduce the bug?
Have a folder with lots of files in .gitignore, start codex, still get warning.
What is the expected behavior?
_No response_
Additional information
_No response_
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗