fix(coding-agents): don't let one stray file abort the Claude history import - #3778
Open
feniix wants to merge 2 commits into
Open
fix(coding-agents): don't let one stray file abort the Claude history import#3778feniix wants to merge 2 commits into
feniix wants to merge 2 commits into
Conversation
… import claudeHistory prefilters project directories by encoded NAME and handed each match to jsonlFiles, which did existsSync — true for a regular FILE — and then readdirSync, which throws ENOTDIR. That escaped importLocalHistory, which documents that it never throws, and whose caller (installer.ts importConversations) has no catch: one junk file in ~/.claude/projects killed the whole --import-conversations run rather than costing that entry. Fixes vectorize-io#3771. The guard belongs in the listing itself, not at the call site: jsonlFiles already owned "there is nothing here to list" via existsSync, and a stray file is that same case reached through a different errno. One listDir helper now holds it for the module, which also closes the identical hole one level up — a regular file at ~/.claude/projects or ~/.dsh/sessions was equally fatal — and lets dshHistory drop its own copy of the try/catch. Dropping the existsSync precheck also sheds a stat per candidate directory (~100 on a real machine) and its TOCTOU window. The Claude regression test fails with the reported ENOTDIR without the guard; the dsh test covers the reader whose loop this refactors, which had no coverage through importLocalHistory at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Strix Security ReviewWarning This pull request has 17 commits after the last Strix review ( No security issues found. Updated for Reviewed by Strix |
6 tasks
…stray-file # Conflicts: # hindsight-integrations/coding-agents/src/core/history.ts
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.
Summary
claudeHistoryprefiltered~/.claude/projectscandidates by encoded NAME and handed each match tojsonlFiles, which didexistsSync— true for a regular file — and thenreaddirSync, which throwsENOTDIR. That escapedimportLocalHistory, which documents that it never throws, and whose caller (installer.tsimportConversations, line 1241) has no catch of its own: one junk file killed the whole--import-conversationsrun instead of costing that entry.jsonlFilesalready owned "there is nothing here to list" viaexistsSync, and a stray file is that same case reached through a different errno. OnelistDirhelper now holds it for the module.~/.claude/projectsor~/.dsh/sessionswas equally fatal — and letsdshHistorydrop its own copy of the try/catch.existsSyncprecheck sheds astatper candidate directory (~100 on a real machine, per theclaudeHistorydocstring) and the TOCTOU window between check and read.Closes #3771.
Type of change
Test plan
src/core/history.test.ts— new Claude regression test seeds one real session plus a stray file named like the project dir for a subdirectory. Verified it fails with the exact reported error when the guard is reverted (Error: ENOTDIR: not a directory, scandir '…/.claude/projects/-Users-x-dev-myrepo-sub') and passes with it.importLocalHistorycoverage at all. It stubs$DSH_HOMEso it can't read a developer's real sessions. Not a regression test — it passes either way.npx vitest run src/core src/installer.test.ts→ 579 passed../scripts/hooks/lint.shclean.Notes for the reviewer
feat/pi-harnesshas a call-site try/catch inpiHistory(fde3bb3) that this makes redundant — it should collapse tolistDirwhen the branches meet, andprimeAgentHistorygets the fix for free.codexHistory's walk is guarded at the opposite altitude (one unstattable entry discards every file found so far and reports "no past sessions found on disk"),importLocalHistorystill has no top-level backstop for its never-throws contract, andinstaller.ts:1258-1259creates its tmpfile outside the try whose comment says a failed backfill must not fail the install.🤖 Generated with Claude Code