Repository navigation
Conversation
… window Every usage scan read and parsed every file in OpenCode's pre-migration JSON store, even though records older than the window are dropped afterwards. The walk already stats each entry, so use that mtime to skip the read when a legacy message was last written before the window opened, as the transcript reader already does. Skipped files are no longer returned, so they stop counting towards the source's skippedFiles. A store holding only old legacy files is still reported as present.
…ymlinks The walk asked readLink about every entry before stat, and readLink fails, with a constructed error, for everything that is not a symlink. On a store of 30,000 legacy messages that was about half of the walk. Stat entries first, four at a time in batches of 256, and ask whether one is a symlink only where the answer changes the result: before descending into a directory, before reading a file, and when stat itself fails. Symlinks are still never followed and results are unchanged.
Contributor
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR changes a production OpenCode usage/metering scan by adding an mtime-based skip gate and concurrent batched filesystem traversal, altering which legacy files reach aggregation. The scope is focused and tested, but its metering-path impact and automatic suppression of substantial processing warrant human review. You can add or adjust custom eligibility rules. Learn more. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
After #17254, a usage scan no longer reads OpenCode legacy message files older than the window, but it still walks them, and the walk is now most of the OpenCode scan. For every entry under
<data dir>/storage/messageit callsreadLinkand thenstat, one entry at a time.readLinkfails for everything that is not a symlink, so on a store of 29,998 legacy files in 796 session directories that is about 30,800 failed calls, each building an error, before the 30,800 stats.Measured over those entries in isolation (sequential, Node v24.3.0, warm cache):
readLinkthrough EffectFileSystemstatthrough EffectFileSystemEffect.fnspan around the pairChange
Stat entries first, four at a time in batches of 256, then handle each batch in directory order. Ask
readLinkonly where the answer changes the result:statfails with something other than not-found, so a symlink loop or a link to an unreadable target is still skipped rather than reported as an error.On the store above that is 30,794 stats and 796
readLinkcalls instead of 30,794 of each. Results are unchanged: same records, same file entries in the same order, samemissing/errorreporting, and symlinks are still never followed.It stays on Effect's
FileSystem, in line with #17615. That service has no lstat andreadDirectoryreturns names only, soreadLinkremains the symlink test. Anode:fswalk using dirent types measured about 90 ms faster for the walk alone, which did not seem worth leaving the service for.Left out on purpose: skipping a whole session directory by its own mtime. OpenCode wrote these files in place (
Bun.write(target, ...)instorage.tsfrom sst/opencodef993541e0b, September 2025, onwards), which does not update the parent directory's mtime, so that skip could drop in-window messages.Scope and approval
Focused performance fix with no intended behavior change: one loop in one reader in
packages/provider-opencode, plus tests. No prior issue or discussion.Stacked on #17254. This branch contains that PR's commit (
f8c09d0743) and one commit of its own (5309804d63); only the second is this change. The gain depends on #17254: without its mtime prefilter every legacy file is still read, so every file still needs its symlink check, and I measured no difference onmainalone. Whichever of the two lands second can be rebased.Verification
Environment: Linux, Node v24.3.0, real OpenCode data directory (
~/.local/share/opencode, opened read-only, 29,998 legacy files / 500 MB, all older than the window), 30 day window (sinceMs = now - 30d - 36h).readOpenCodeUsagetimed five times in one process withNodeServices.layer, withperf_hooks.monitorEventLoopDelayfor the event-loop figure. The parent is #17254's head,f8c09d0743. Three alternating rounds; the machine was running other CPU-bound work at the time, which is why rounds differ.The ~35-45 ms event-loop delay is present in both and comes from the SQLite read. An earlier revision of this change (same walk, before the batches of 256) showed 1-4 ms when the legacy walk ran on its own, at concurrency 1, 4 and 8; I did not repeat that isolated run on the final revision.
Same results as the parent on the real store: for the 30 day window the 172 serialised records and their dedupe keys are identical. With
sinceMs = 0, which reads the 3,358 legacy files not already in SQLite, the 84,050 records are identical.Tests in
packages/provider-opencode/src/server/usage.test.ts:never follows symlinks in the legacy OpenCode store: a symlinked message, a symlinked stale message, a symlinked directory, a cycle, a dangling link and a self-referencing link are all ignored without an error, and a store made only of links is reportedmissing. It fails if the symlink check is disabled.reads a large legacy OpenCode session in directory order: 600 messages in one directory come back as 600 file entries inreadDirectoryorder, across batch boundaries.Commands, run from
packages/provider-opencode:vp test run src/server/usage.test.ts: 4 passed.vp lintandvp fmton the two touched files: clean.tsc --noEmit: no errors.Not checked: macOS or Windows; a cold page cache or a slow or network filesystem; the end-to-end Usage page load in a client. There is no committed test for a non-not-found
statfailure on a regular entry (it needs a permission-denied fixture, which does not fail when tests run as root); a reviewer's scratch comparison of parent and this change covered EACCES, ELOOP and ENOTDIR cases with matching results.Model: Claude Opus 5.5. Harness: Claude Code in T3 Code. Reviewed by GPT-6.1-Sol via Codex.