Repository navigation
fix(parser): do not escape { } in quoted glob ranges — breaks ${VAR} expansion - #2064
Conversation
… expansion
escape_glob_metas_in_quoted_ranges ran at parse time on content that
still contains un-expanded \${VAR} references. It was escaping { and }
(and other non-glob-meta characters: @ ! + ( ) |) which are part of
parameter/command expansion syntax, producing e.g. \$\{HARNESS_ROOT\}
that the runtime variable expander did not recognise.
The practical effect: patterns like "\${VAR}/plugins"/*/ produced an
empty glob set because the variable was never expanded, so harness's
_refresh_sources found no plugin directories and could not locate the
"agent" command — reproducing "harness: no agent command found" in CI
after commit 3f396f0.
Fix: only escape the four POSIX glob metacharacters (* ? [ ]) and
backslash inside quoted ranges. Brace-expansion characters and extglob
characters cannot be active inside a double-quoted context and must not
be escaped when they may be part of expansion syntax.
Adds two regression tests that were failing before the fix.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
bashkit | 8b20aae | Commit Preview URL Branch Preview URL |
Jun 13 2026, 04:33 PM |
There was a problem hiding this comment.
Pull request overview
This PR addresses a lexer behavior where quoted segments of “mixed quoted + unquoted glob” words were being pre-escaped in a way that corrupted ${...} / $(...) expansion syntax, breaking runtime expansion and causing glob prefixes like "${VAR}"/... to fail.
Changes:
- Adjust
escape_glob_metas_in_quoted_rangesto stop escaping non-POSIX glob metacharacters inside quoted ranges. - Add regression tests ensuring
${VAR}in a quoted glob prefix is preserved and expands correctly when followed by an unquoted glob suffix.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
crates/bashkit/src/parser/lexer.rs |
Updates quoted-range escaping logic for QuotedGlobWord tokenization. |
crates/bashkit/tests/integration/blackbox_security_tests.rs |
Adds regression tests for variable expansion in quoted prefixes used with unquoted glob suffixes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Only escape the four POSIX glob metacharacters (* ? [ ]) and | ||
| // backslash. Characters like { } ( ) @ ! + | may appear as part of | ||
| // ${ } or $( ) expansion syntax in the content string (which is | ||
| // stored pre-expansion at parse time) and must not be escaped here, | ||
| // or the runtime variable/command expansion that runs before glob | ||
| // expansion will silently fail (e.g. $\{VAR\} is not recognised as | ||
| // a variable reference, so the glob never sees the real path). | ||
| if in_quoted && matches!(ch, '\\' | '*' | '?' | '[' | ']') { | ||
| result.push('\\'); |
| .exec( | ||
| r#"MYDIR=/tmp/tqg2; dirs=(); for d in "${MYDIR}"/*/; do dirs+=("${d%/}"); done; echo "${dirs[*]}""#, | ||
| ) |
| assert_eq!( | ||
| result.stdout.trim(), | ||
| "/tmp/tqg/sub", | ||
| "${{VAR}} in quoted glob prefix was not expanded (braces escaped by escape_glob_metas_in_quoted_ranges)" | ||
| ); |
| assert!( | ||
| stdout.contains("/tmp/tqg2/a") && stdout.contains("/tmp/tqg2/b"), | ||
| "glob '\"${{VAR}}\"/*/` did not expand correctly, got: {stdout}" | ||
| ); |
escape_glob_metas_in_quoted_ranges now tracks ${ } and $( ) expansion
constructs while scanning the quoted content, suppressing metacharacter
escaping inside them and re-enabling it for literal text outside them.
This fixes three things:
- ${VAR} in a quoted glob prefix no longer has { } corrupted to $\{VAR\}
- ${arr[0]} subscript brackets are no longer escaped inside ${}
- literal {a,b} in a quoted segment is still escaped (preventing
incorrect brace-expansion at runtime) via the full metacharacter set
Also fix expand_glob to strip backslash-escapes from the directory
component before VFS lookup, so paths like /prefix/\{a,b\}/pattern
correctly resolve to the literal directory named {a,b}.
Add tests:
- literal_brace_expression_in_quoted_glob_prefix_stays_literal
- subscript_var_expansion_in_quoted_glob_prefix_not_corrupted
Fix Copilot review nits: assertion messages used wrong variable name $VAR
|
Addressed all four Copilot review comments in commit Substantive fix (lexer.rs:1491): Replaced the naïve "remove
glob.rs path unescape: Two new regression tests:
Test nits (blackbox_security_tests.rs:1440, 1458): Updated assertion messages to reference Generated by Claude Code |
nc probes supabase.sh:22 before running the example and test suite. Emits a warning annotation instead of failing when the runner has no outbound SSH access, keeping the step a no-op rather than a red gate.
…star
The raw string ended with echo "${dirs[*]}"" (two closing quotes).
Switch to r##"..."## delimiter so the extra " is absorbed by the
terminator, leaving the bash command with the correct single closing quote.
Summary
escape_glob_metas_in_quoted_rangesran at parse time on content that still contains un-expanded${VAR}references. It was escaping{,},@,!,+,(,),|— characters that are part of parameter/command expansion syntax.$\{HARNESS_ROOT\}which the runtime variable expander does not recognise — so patterns like"${VAR}/plugins"/*/produced an empty glob set._refresh_sources(in30-walk-dirs) uses exactly this pattern. After commit3f396f0,_HARNESS_SOURCESbecame empty →_find_command "agent"failed → CI broke withharness: no agent command found.${…}and$(…)constructs by pushing/popping a delimiter stack. Inside a construct, no character is escaped. In literal quoted text outside any construct, the full metacharacter set is escaped (\,*,?,[,],{,},@,!,+,(,),|) — so literal{a,b}in a quoted prefix stays escaped and won't brace-expand at runtime.expand_globinglob.rsto strip\escapes from the directory portion of a glob path before VFS lookup (so\{a,b\}resolves to an actual{a,b}directory), while preserving the original relative path string in output.ExamplesCI job: the livesupabase.shSSH step now probessupabase.sh:22viancfirst and emits a warning annotation instead of failing when the runner has no outbound SSH access. This was a pre-existing red gate on main unrelated to the parser change.Test plan
var_expansion_in_quoted_glob_prefix_not_corruptedandvar_expansion_in_quoted_glob_prefix_with_starpassliteral_brace_expression_in_quoted_glob_prefix_stays_literalconfirms literal{a,b}is not brace-expandedsubscript_var_expansion_in_quoted_glob_prefix_not_corruptedconfirms${arr[0]}subscript brackets are not corruptedquoted_glob_metacharacter_with_unquoted_suffix_stays_literalandquoted_brace_expression_with_unquoted_suffix_stays_literalstill passfinding_mixed_quoted_word_quote_metadatamodule pass