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 = true to preserve/remove ignored files correctly.
  • New test ignored_large_directories_do_not_trigger_warning verifies 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_

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗