Skip to content

perf(sessions): bound flat Codex directory entry traversal - #4857

Open
ScriptedAlchemy wants to merge 15 commits into
masterfrom
fix/4756-codex-directory-entry-budget-20261110
Open

ScriptedAlchemy wants to merge 15 commits into
masterfrom
fix/4756-codex-directory-entry-budget-20261110

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

Refs #4756 — partial Codex directory-work bound (not Fixes)

Root cause and focused correction

The initial CodexScanPhase::Directories phase charged queued and discovered subdirectories against its per-turn budget, but did not charge ordinary directory entries read from ReadDir. A flat ~/.codex/archived_sessions directory containing many .zst or other non-directory names could therefore be fully enumerated by one scheduler call, before the separately bounded file-candidate phase. The reported corpus includes 1,385 Codex .zst entries; those entries are not accepted as JSONL transcripts by this reader, so their enumeration should not monopolize a tick.

The source now counts every entry consumed from the structural ReadDir iterator against its own max_files × (MAX_SCAN_DEPTH+2) work window. When the limit is reached, the existing process-owned iterator remains intact for the next call. Child-directory structural work and the candidate-file phase retain their independent existing limits. There is no new format, scheduler, durable storage, or SQL writer behavior.

Nonvacuous behavioral regression

A real filesystem fixture creates 64 ignored .zst entries and one admitted .jsonl in a flat archived Codex directory, then runs max_files=1 discovery. The first call must yield inside the directory phase with its live iterator retained, rather than consuming the archive and entering the file phase in one call; the previous implementation fails this assertion. Repeated bounded calls must converge on exactly the eligible JSONL file and earn the complete frontier, with an empty subsequent idle pass. No other test or assertion was removed.

Verification and boundaries

Kev/decision-model PRs and mixed #4776 remain DRAFT / never merge or auto-merge; release #4763 read-only. No production deployment, infrastructure, tags, credentials, Kody/Copilot/delegation, weakened CI, other owner edits or schedule changes.


Devin Review

Refs #4756

A flat archived directory with many .zst/non-JSONL entries previously
consumed its entire ReadDir iterator in one structural scan turn, outside
the directory-work budget. Charge directory entries independently of child
directory traversal while retaining the iterator across turns.

Add a real archived-directory regression requiring the first scan to yield
in the directory phase, then converge on exactly the eligible JSONL path.

Partial performance repair only; durable restart continuation and affected
host CPU/write-latency acceptance remain outstanding.
@changeset-bot

changeset-bot Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 294ea91

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Newer findings are available below. Devin Review posted a newer report on this PR, in addition to the findings presented here.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

Refs #4756

Keep unsettled directory name witnesses scoped to traversable child
buckets and JSONL candidate paths. Compressed rollouts and other ignored
file churn must not independently invalidate the candidate witness.

Cover ignored additions and renames, candidate admission, and child
bucket discovery with a real-filesystem regression.

Copy link
Copy Markdown
Owner Author

Refs #4756 — follow-up Codex discovery witness repair (partial)

Published 0390a59ed by exact expected-old-head 835291a and non-force fast-forward on this existing issue-specific PR. The commit touches only codex.rs and its existing Rust discovery tests.

Cause and change: Even after limiting structural ReadDir entries per scheduler turn, the unsettled-directory name fingerprint still collected and sorted every entry, including ignored compressed .jsonl.zst rollouts. A coarse timestamp that did not change would then treat churn in those ignored names as candidate-corpus mutation, forcing an unnecessary historical validation sweep. The fingerprint now retains only traversable child directories and names with the exact .jsonl extension accepted by the file-candidate phase. A .jsonl symlink remains tracked, and a new child directory remains tracked. Native stat/change-time invalidation remains authoritative.

Nonvacuous filesystem regression: 128 ignored .jsonl.zst additions and an ignored rename preserve the candidate fingerprint; adding and removing an eligible .jsonl changes/restores it; adding a child directory changes it. The old implementation fails the first assertion. No test or CI gate was weakened.

Detached exact diff is one commit ahead, two files, +46/-1; independently checked the diff and branch head. Exact-head Bazel CI run 42769750119 was queued at inspection; there is no local checkout or Bazel executable, so no Rust test pass or incident-scale performance measurement is claimed. No Cargo used.

This is not Fixes #4756. The process-held ReadDir position still does not survive daemon restart; multi-hour CPU/writer-latency acceptance remains unproven. Keep #4756 open and this PR unmerged pending those proofs. #4793, mixed draft #4776, Kev/decision-model drafts, release #4763, other branches, and task schedules were untouched.

Refs #4756

Apply the two exact formatting hunks emitted by Bazel CI; preserve every behavioral test assertion.

Copy link
Copy Markdown
Owner Author

Refs #4756 — CI repair published as b71eb2b on this issue-specific PR.

The exact preceding-head Bazel run #42769750119 failed //crates/tracedecay-sessions:rustfmt_check_2024. Its log emitted exactly two formatting hunks in codex/tests.rs (the 128-archive and 64-archive fixture writes). Applied those hunks without changing source behavior or test assertions. GitHub compare independently confirms one commit, one file, exact parent 0390a59e, and the branch update used expected_sha=0390a59e, force=false.

New exact-head hosted Bazel run #42775257282 is queued. No Rust test pass, durable mid-directory restart-continuation proof, or incident-scale CPU/SQLite write-latency acceptance is claimed. This remains Refs #4756; keep the issue open and PR unmerged. The unrelated projection-queue lane #4793 currently has Linux global-db timeout/MCP failures on its preceding head and was not modified; protected #4776/Kev drafts, release #4763, CI policies and peer branches remain untouched.

Copy link
Copy Markdown
Owner Author

Refs #4756 — eliminate eager full-archive name enumeration (partial)

Published 3912dd4255e3 to this issue-specific branch via an exact-head, nonforce fast-forward from b71eb2bfee4e1aee8f7c22f0f74cc97cc76248ab.

Root cause: the recently bounded structural ReadDir walk still called codex_directory_witness first. An unsettled directory synchronously enumerated every entry and sorted all eligible names to build its idle-change witness before the entry budget was enforced. A flat archive therefore still had an unbounded preliminary traversal.

Source fix: obtain the native directory identity only; retain an incremental, constant-memory, order-independent candidate-name digest on the same ReadDir iterator that is already sliced by the structural work budget. Publish the completed name witness to the retained directory authority only after the iterator reaches EOF. The standalone idle-check fingerprint uses the same digest algorithm. Ignored .zst entries remain excluded from candidate identity; eligible .jsonl files and subdirectories remain included.

New real-filesystem regression: create 128 ignored .jsonl.zst archive entries plus one eligible JSONL file under an unsettled directory; assert that the first bounded pass retains an unfinished iterator without an eager name fingerprint, that later passes cover the exact JSONL file, and that the final retained name witness equals an independent full enumeration. No assertions or CI gates were weakened.

Review: independent GitHub compare verifies one commit with only codex.rs and codex/tests.rs changed (112/38 and 75/0 insertions/deletions respectively), and a direct parent of the reviewed branch head. Exact-head Bazel CI is queued; no Rust test pass or incident-scale performance result is claimed. No Cargo used.

This specifically removes the eager unbounded name-scan before bounded initial discovery; idle unsettled-directory rechecks, durable mid-directory restart continuation, and measured 4-hour CPU / SQLite write latency acceptance are not yet proved. Keep #4756 open, retain Refs #4756, and do not merge on this evidence alone. #4793, protected Kev/decision-model and mixed #4776 drafts, release #4763, other branches and schedules were untouched.

Copy link
Copy Markdown
Owner Author

Refs #4756 — exact-head CI compile and format repair (partial)

Published 2598f104 to this existing issue-specific PR via an exact expected-old-head, non-force fast-forward from 3912dd42.

Confirmed CI root causes: the preceding Bazel run #42781976950 produced two E0425 errors in codex.rs (lines 2311, 2334): after refactoring ReadDir into CodexDirectoryIterator, child-queue construction still referenced the removed local depth instead of directory.depth. Linux, Windows, and Clippy failures cascade from those same two compilation errors. The repository rustfmt gate independently identified a single let idle formatting hunk in the new real-filesystem regression.

Changes: change both depth references to directory.depth and apply exactly the emitted rustfmt hunk; no traversal behavior, regression assertions, CI gates, or other files changed. Detached GitHub commit comparison verifies a single parent and only two files (+6/-3).

New exact-head Bazel CI #42898370855 is running. There is no local Bazel checkout in this run; this is not a claim of a passing Rust test.

Still unresolved: durable mid-directory traversal resume across daemon restarts; end-to-end provider-separated CPU and SQLite bounded-write evidence on an incident-scale corpus. Keep #4756 open and this PR unmerged pending proof; do not use Fixes #4756 yet. The separate projection-lane #4793, protected Kev/mixed draft #4776, release #4763, all other owner branches and automation schedules were untouched.

Copy link
Copy Markdown
Owner Author

Refs #4756 — published CI compilation fix (partial only)

Exact-head Bazel CI #42898370855 failed Linux fleet, Clippy and Windows because CodexRetainedScan consumed the Option<DirectoryStatWitness> with stat.map(...) and then reused stat for the incremental directory fingerprint (E0382, codex.rs:2229). The identical error cascaded across those jobs; repository gates passed.

Published 4b3e4bab to this existing issue-specific PR via an exact expected-old-head (2598f104), non-force fast-forward. The one-line source fix uses stat.as_ref().map(...), borrowing the witness before the subsequent stat.filter(...). GitHub comparison independently confirms one commit, one file, one insertion and one deletion. No other branch was touched.

New exact-head Bazel CI #42904574809 is queued; no Rust test pass is claimed. The in-process structural and fingerprint work bounds remain partial: native ReadDir state does not survive a daemon restart, and the original incident's 4-hour CPU and 125–160 ms SQLite write profile has not been reproduced and resolved. Keep #4756 open; no Fixes, no merge. Protected #4776/Kev drafts and read-only release #4763 untouched. No Cargo, CI weakening, or schedule changes.

Refs #4756

A budgeted directory iterator may span multiple ticks while new entries
arrive. Verify its opening stat witness when it is exhausted and keep
coverage incomplete after an observed change, then require a fresh sweep.
Add a real filesystem regression for mid-scan mutation and convergence.
Refs #4756

The first sweep may already emit the newly arrived rollout even when
its directory witness requires another pass. Assert complete source
coverage across both the invalidated and stable sweeps.
Refs #4756

Retain the opening stat on the directory iterator itself so changes
across structural memory windows still invalidate sweep completion,
even when the prior chunk's directory list was released.
Refs #4756

Match the repository's multiline Rust formatting without changing the
mutation fence or the real filesystem regression.

Copy link
Copy Markdown
Owner Author

Refs #4756 — published a second correctness slice on this existing issue PR (not Fixes).

Newly identified race: an unfinished Codex ReadDir can span numerous bounded scheduler turns. The previous implementation remembered a directory stat at iterator creation and then completed the sweep from that stale iterator even if the directory gained/removed a file before exhaustion. The iterator may omit the intervening entry; asserting complete discovery from that mixed snapshot was not justified. When the directory list is released between structural chunks, the original directory-index lookup also ceases to own that stat witness.

Published four exact-expected-head, nonforce fast-forward commits directly to #4857, preserving its earlier flat-entry work and all peer branches:

  • 1fc5852: recheck the directory stat at iterator exhaustion, with a real mid-scan mutation regression that refuses premature complete coverage and requires a stable later sweep.
  • 0978194: ensure the regression accumulates admitted sources across both sweeps, since the new file may already be emitted in the invalidated first pass.
  • 3c99fb0: carry the opening stat with the iterator so the fence still works if a bounded structural chunk retires its original directory list.
  • a2cf71a: formatting-only follow-up.

I fetched and reviewed each published commit's actual GitHub patch: all changes are confined to codex.rs and its existing codex/tests.rs module; no API/CI/scheduler/release alterations, test removals, or branch rewrites. New exact-head hosted Bazel CI #42916366742 is queued. No Rust compilation, test result, independent approval, or macOS incident benchmark is claimed yet: local checkout/Bazel are unavailable, and Cargo was not used.

This is a source-authority safety fix, not durable mid-directory resume across process restart. The original ~4h/~1.7-core import, 125–160ms writer latency and provider-separated restart continuity remain unproven, and #4793's independent SQLite/write-recovery lane is untouched. Keep PR unmerged and issue OPEN until relevant exact-head nonzero Bazel/host tests, independent review and master incident-scale acceptance. Kev/decision-model and mixed #4776 remain draft-only; release #4763 untouched; no production/infra/tags/credentials or other schedules changed.

Refs #4756

An unsettled native change clock can coalesce directory mutations
across a retained ReadDir walk. Matching opening and closing stat bytes
are not enough to assert complete discovery. Require a settled closing
witness before certifying the sweep; retain in-progress coverage until
validation can establish a stable directory.

Add a real bounded-read regression that aliases the native stat across
a mid-scan JSONL arrival and rejects a false complete watermark.
devin-ai-integration[bot]

This comment was marked as resolved.

Refs #4756

Revert an over-conservative unsettled-stat condition that could
prevent completed fresh scans until the filesystem clock settled.
Instead, recheck each retained file-phase directory identity at EOF
so files arriving after the structural walk cannot be silently omitted
from a complete frontier. Preserve the existing per-turn entry budget.

Add a real filesystem regression inserting JSONL while file-phase
ReadDir is suspended; full coverage must remain in progress.

Copy link
Copy Markdown
Owner Author

Refs #4756 — published a focused Codex file-phase coverage correction on the existing issue PR.

New source-level race: The bounded CodexScanPhase::Files retains a native ReadDir across scheduler calls. A new .jsonl name can arrive after the structural directory witness has been recorded but while the file iterator is suspended. Previously the file phase could reach EOF and publish a Complete frontier from the potentially stale iterator without rechecking the directory identity.

Published net change: 0d4be4e checks the native directory witness at file-iterator EOF and retains InProgress coverage if it changed. A new real-filesystem Rust regression holds a bounded file-phase iterator, creates an eligible .jsonl, proves the directory witness changed, drains the iterator and refuses a false Complete watermark. The test should fail before this fix. The current branch is an exact-head nonforce fast-forward; comparison against its preceding tested source head has only the two Codex files, net +72/-1. No peer PR was changed.

Boundaries: An initial conservative coarse-clock check in ecc476e was replaced by this narrower fix because requiring every freshly changed directory to become clock-settled before any complete frontier could over-defer normal first-time scans. Final source restores the prior structural-end check and adds the independent file-phase identity fence. Coarse timestamp aliasing, durable mid-directory restart continuation, and affected-host multi-provider CPU/write-latency proof remain unsolved.

No local checkout/Bazel is available and no Rust pass is claimed. Exact-head Bazel CI 42930379989 is queued; require actual nonzero //crates/tracedecay-sessions:unit_test, Rustfmt, Clippy, hosted integration/review and tests before merge. The previous-head run was superseded before its Linux/Clippy completion. Keep Refs #4756, issue open, and this PR unmerged. The independent projection queue PR #4793, held #4776/Kev drafts, release #4763 and every schedule remain untouched.

devin-ai-integration[bot]

This comment was marked as resolved.

Refs #4756

After a retained file iterator finishes under a coarse directory clock,
reopen its eligible-name walk in bounded chunks and compare against the
structural name fingerprint before granting complete coverage. Preserve
Windows convergence without assuming directories have settled timestamps.

Regress a late eligible rollout with an intentionally aliased coarse
stat witness; keep all file and directory per-call budgets unchanged.
Refs #4756

An initial coarse-clock name fingerprint must be independently checked
when the file iterator closes, even if its clock has since settled.
Do not poison a temporarily suspended structural directory batch.
Keep the aliasing regression deterministic across slow test hosts.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Devin Review

Comment on lines +2527 to +2533
} else if closing.is_some_and(|stamp| !stamp.settled)
&& resume_directories.is_none()
{
// An unfinished structural iterator is
// resumed below; only its final pass may
// certify coverage without a name witness.
scan.complete = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Chunked archive misses newly added rollouts

When a rollout arrives during a chunked file walk with an aliased directory timestamp, resume_directories permits complete coverage. The resumed structural walk does not scan rollout files, so the missed rollout remains absent from the completed frontier.

Learn more

A retained directory iterator can be paused for a file-phase pass when directory retention fills. The file iterator can miss a newly added rollout whose directory timestamp aliases the original on a coarse filesystem. With resume_directories present, this branch leaves scan.complete true despite the missing entry-name witness. The subsequent resume transition clears the directory identity; the resumed structural iterator scans only for child directories and cannot add the omitted file or restore that identity's fingerprint. Completion can therefore persist without having observed the rollout.

Example: A flat archive fills the retained-directory budget while its structural ReadDir remains open. A .jsonl file arrives after the file reader has passed its position, with the same coarse native directory timestamp. The file reader omits it; resuming the structural reader does not emit it; the sweep still reports complete.

Recommended fix: Retain a verifiable name witness across chunk transitions and compare it against a fresh bounded listing after the structural iterator finishes, or mark coverage incomplete when a file walk ends unsettled without such a witness. Cover a chunked archive whose directory timestamp aliases a new .jsonl during the paused file walk.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant