fix(shell): use range scans instead of LIKE in Workspace rm/glob#1716
Open
mattzcarey wants to merge 2 commits into
Open
fix(shell): use range scans instead of LIKE in Workspace rm/glob#1716mattzcarey wants to merge 2 commits into
mattzcarey wants to merge 2 commits into
Conversation
D1 can reject the LIKE ? ESCAPE ? queries in deleteDescendants and the glob prefilter with 'D1_ERROR: LIKE or GLOB pattern too complex'. Replace them with range predicates on the path primary key, which avoid the LIKE limit, use the index directly, and need no %/_ escaping. Fixes #1539
🦋 Changeset detectedLatest commit: 3ccfe70 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
agents
@cloudflare/ai-chat
@cloudflare/codemode
create-think
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
This was referenced Jun 11, 2026
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.
Note
This is a Fable experiment (model-authored PR). Please close if it adds no value.
Fixes #1539
Problem
Workspace.rm(path, { recursive: true })deletes descendants viadeleteDescendants(), which usedWHERE path LIKE ? ESCAPE ?. On D1 this can fail with:The
glob()prefilter used the sameLIKE/ESCAPEconstruction and had the same exposure.Fix
Replace the
LIKEqueries with range predicates over thepathprimary key:deleteDescendants(dirPath)— both the R2-key collection and the SQLite delete now usepath >= '${dirPath}/' AND path < '${dirPath}0'.'0'is the character immediately after'/', so the half-open range covers exactly the paths strictly underdirPath— same semantics as the oldLIKE '${dir}/%', including the prefix-sibling boundary (/skill-extra,/skill0survive anrm -r /skill).glob(pattern)—getGlobPrefixreturns either a/-terminated directory prefix (range scan as above) or, for wildcard-free patterns, the whole pattern (now an exactpath = ?match; the regex filter already reduced it to exact).escapeLike()helper andLIKE_ESCAPEconstant — there are no remainingLIKEqueries in the package.The range form also scans the
pathprimary key index directly instead of going through SQLite's LIKE optimizer, and needs no escaping of%/_in path names.Tests
Added regression tests in
packages/shell/src/tests/workspace.test.ts:rm -rremoves a deep skill-shaped tree (the exact reproduction shape from Workspace.rm recursive: replace LIKE pattern with range scan #1539:SKILL.md,scripts/,references/,assets/)rm -r /skillpreserves prefix siblings/skill-extra/…and/skill.txtrm -r /dirpreserves the upper range boundary:/dir0and/dir00/…survive (the'0'-successor edge specifically)%/_in the name and doesn't leak into prefix-sibling dirs (/a-extra,/a0) outside the glob prefixFull shell suite: 235 passed, 1 skipped. Existing LIKE-injection security tests (
%/_dir names) still pass unchanged.Notes for maintainers
dirPathcan never be/here —rm()throwsEPERMfor root before reachingdeleteDescendants— so the${dirPath}/lower bound is always well-formed.pathcolumn uses the default BINARY collation, and the boundary characters are ASCII, so the range compares bytewise identically under SQLite (DO storage) and D1.@cloudflare/shellpatch).${dirPath}/, range applied to both R2 collection and delete); implemented independently with a${dirPath}0upper bound instead of${dirPath}/�so paths containing astral-plane characters (which sort above U+FFFF in UTF-8 byte order) are still covered.