codex icon indicating copy to clipboard operation
codex copied to clipboard

Ghost snapshot warns about ignored directories

Open baron opened this issue 1 month ago • 1 comments

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

baron avatar Nov 30 '25 09:11 baron

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #7369
  • #7313
  • #7133
  • #7067
  • #6977

Powered by Codex Action

github-actions[bot] avatar Nov 30 '25 09:11 github-actions[bot]