Repository navigation
fix(env): keep backslashes literal in single-quoted dotenv values - #14228
Conversation
Since 2026.10.3 the vendored dotenv parser decoded `\\` and `\'` inside single quotes, so `UNC='\\server\share'` loaded as `\server\share` and `DIR='C:\temp\'` failed as an unterminated quote. Single-quoted values are literal again, as they were with dotenvy. Fixes #14227 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4G5T5QH17Z9FYBA3F9SMQ1X
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (3)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughSingle-quoted dotenv values now preserve backslashes literally. Unit and end-to-end tests cover Windows paths, literal ChangesSingle-quoted dotenv parsing
Priority: ➖ Normal Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix · Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to Single-quoted dotenv values now retain literal backslashes, including UNC prefixes and trailing backslashes. The supplied unit and end-to-end checks cover these cases, and no merge-blocking behavior issue is established. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)✅ Passed checks (4 passed)Full details: Docstring CoverageExplanation Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 2 files. (1 skipped: 1 unsupported.)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…14231) <!-- entire-trail-link-start --> https://entire.io/gh/jdx/mise/trails/614 <!-- entire-trail-link-end --> Since 2026.10.3, mise reads a dotenv value as exactly one of single-quoted, double-quoted, or unquoted. Before that, values were read like a shell word: quoted and unquoted parts next to each other were joined and their quotes removed. Common `.env` lines broke: ```sh A='it'\''s' B=a'b c'd C="one"'two'three ``` | | before 2026.10.3 | 2026.10.3–2026.10.6 | this PR | |---|---|---|---| | `A` | `it's` | syntax error | `it's` | | `B` | `ab cd` | `a'b c'd` (quotes kept) | `ab cd` | | `C` | `onetwothree` | syntax error | `onetwothree` | The syntax error hurt most under the `env_file` setting (`MISE_ENV_FILE=.env`): mise warned and dropped every line after `A`. In `_.file`, it failed the whole config. This PR joins value segments again, as before 2026.10.3. These stay the same: - Single-quoted parts are fully literal: no escapes, no `$VAR` substitution (#14228). - Variables resolve against the file's own earlier assignments first. - A `${...}` expression is one unit, so quotes in a default (`${NAME:-"a b"}`) don't split the value, and a brace inside a quoted default (`${NAME:-"{}"}`) doesn't end the expression. ### Compatibility notes - Quotes inside an unquoted value are now removed again. A JSON value that must keep its quotes needs single quotes: `CONFIG='{"debug": true}'`. Before 2026.10.3, `CONFIG={"debug":true}` gave `{debug:true}`; 2026.10.3–2026.10.6 kept the quotes. Unquoted JSON with spaces was a syntax error before 2026.10.3, so files written for older mise are unaffected. - A quote partway through a value opens a quoted part only when its partner comes later on the same line. Otherwise it's a literal character, so `NAME=O'Brien` and `GREETING=it's great` read as written, as they did in 2026.10.3–2026.10.6 (dotenvy rejected both). Only a value's opening quote may span lines, as before. Quotes and comments are read left to right, so a partner inside a trailing comment still counts (as in dotenvy, `a'x #y'` is `ax #y`): write `NAME="O'Brien" # owner's name`. - Text after a closing quote is now part of the value (`KEY="a"b` gives `ab`), so `mise_dotenv::ParseErrorKind::TrailingCharacters` no longer occurs and is removed. ### Validation - New unit tests cover concatenation, the quote/comment interactions, substitution across segments, and dotenvy 0.15.7's own `test_parse_value_escapes` cases (e.g. `KEY4='sweet $\fgs'\''fds'` → `sweet $\fgs'fds`). Existing tests that asserted literal quotes in unquoted values now expect dotenvy's results. - `render_value` now quotes any value containing `'` or `"`. Its round-trip property tests pass with 20,000 cases. - New e2e test `e2e/env/test_env_file_quote_concatenation` covers `_.file` and `MISE_ENV_FILE`. It fails without this change with the old syntax error. - A differential fuzz against dotenvy 0.15.7 compared ~936k single-line values that dotenvy accepts. With the divergences that predate this PR filtered out, the only difference left is a dotenvy quirk that keeps one space before a comment after `$VAR`. - `cargo clippy --workspace --all-features --all-targets -- -D warnings` passes, and `cargo +1.95 check -p mise-dotenv` passes. Divergences from dotenvy that predate this PR and are unchanged: an unquoted `\n` stays a literal backslash-n (dotenvy wrote a newline), a trailing lone `\` or `$` is kept (dotenvy dropped it), and `\ ` inside double quotes is kept as written. This branch is stacked on #14228 and includes its commit. Merge #14228 first; this diff then shrinks to the concatenation change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) *AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5-5; version: 2.1.293.* <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes how every dotenv file is parsed (including unquoted JSON and post-quote text), which can alter loaded env values versus 2026.10.3–2026.10.6 even when fixing previously broken lines. > > **Overview** > Restores **shell-style dotenv value parsing** in `mise-dotenv` after 2026.10.3 treated each value as only single-, double-, or unquoted. Adjacent segments are joined again (`'it'\''s'` → `it's`, `a'b c'd` → `ab cd`), fixing syntax errors that could drop or abort the rest of a `.env` under `MISE_ENV_FILE` or `_.file`. > > Parsing now walks values with `parse_value`: quotes open a segment only when a closing partner exists on the same line (so `O'Brien` stays literal); single-quoted text stays fully literal; `${...}` defaults treat quoted words as one unit. **`TrailingCharacters` is removed**—text after a closing quote is part of the value. **`render_value`** treats paired quotes like the parser so round-trips stay consistent. > > Docs and new unit/e2e tests document quoting, JSON via single quotes, and `_.file` / `env_file` behavior. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 90d01d1. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Dotenv values now combine adjacent quoted and unquoted segments, removing quote delimiters while preserving single-quoted text literally. * Quoted segments can be followed by additional value text. An unmatched quote within a value is treated literally; unmatched opening quotes still produce an error. * Single-quoted values preserve backslashes, including in paths and literal escape sequences. * Values containing quote characters are now quoted when rendered, preserving them when read again. * **Documentation** * Updated dotenv guidance to explain shell-style quoting, concatenated values, and references to earlier assignments, with examples. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
<!-- entire-trail-link-start --> https://entire.io/gh/jdx/mise/trails/613 <!-- entire-trail-link-end --> ### 🚀 Features - **(upgrade)** add tool_update.global_auto to update every global tool by @jdx in [#14237](#14237) - reuse downloads the server confirms are unchanged by @jdx in [#14222](#14222) ### 🐛 Bug Fixes - **(backend)** stop list() and get() panicking while the tool map reloads by @jdx in [#14240](#14240) - **(bootstrap)** stop reporting freshly cloned repos as dirty with core.autocrlf by @jdx in [#14225](#14225) - **(cargo)** lock the same rust homes the rust plugin resolves by @wislertt in [#14219](#14219) - **(doctor)** show dotfiles repo and origin when history is disabled by @jdx in [#14244](#14244) - **(dotfiles)** replace a junction target when a symlink source changes by @jdx in [#14226](#14226) - **(env)** keep backslashes literal in single-quoted dotenv values by @jdx in [#14228](#14228) - **(env)** join adjacent quoted and unquoted dotenv value parts again by @jdx in [#14231](#14231) - **(history)** describe tracked variant changes by @oppegard in [#14194](#14194) - **(lock)** apply aqua version prefixes before selecting overrides by @nettlesh in [#14218](#14218) - **(rust)** treat rustup profile aliases like their full names by @JamBalaya56562 in [#14220](#14220) - **(schema)** accept task references in a task array by @JamBalaya56562 in [#14229](#14229) - **(self-update)** keep showing the auto-update hint until it is disabled by @jdx in [#14233](#14233) ### 🚜 Refactor - **(lock)** skip aqua prefix ambiguity pass without override prefixes by @jdx in [#14232](#14232) ### 📚 Documentation - explain auto_update's release delay and prefer mise settings KEY=VALUE by @jdx in [#14235](#14235) ### New Contributors - @wislertt made their first contribution in [#14219](#14219) - @oppegard made their first contribution in [#14194](#14194)
Instruction counts
No instruction-count regression above 1%. Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run. Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes.
|
https://entire.io/gh/jdx/mise/trails/612
Since 2026.10.3, mise has processed backslash escapes inside single-quoted
.envvalues. That broke Windows paths loaded through[env] _.fileand theenv_filesetting:UNC_PATH\fileserver\data\share. On Windows this resolves toD:\fileserver\...on the current drive\\fileserver\data\shareDIRC:\temp\Single-quoted values are literal again, as they are in shell, Node's
dotenv, and thedotenvyparser mise used before 2026.10.3. Double-quoted and unquoted values still process escapes.This regression came from #13946, which vendored
dotenv-ng-core. That parser decoded\\and\'inside single quotes. Supporting\'inside single quotes only existed from 2026.10.3 through 2026.10.6, so it is gone again. To embed a single quote, use a double-quoted value.Fixes #14227
Validation: new
mise-dotenvunit test ande2e/env/test_env_file_single_quotes(both fail without the fix);cargo test -p mise-dotenvand the existing.enve2e tests pass.AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5-5; version: 2.1.293.
🤖 Generated with Claude Code
Note
Low Risk
Targeted parser regression fix for single-quoted values only; double-quoted/unquoted parsing unchanged, with new unit and e2e tests.
Overview
Restores literal single-quoted
.envvalues: backslashes are no longer interpreted as escapes (including\'before the closing quote), matching shell, Nodedotenv, and pre-2026.10.3 mise behavior. Double-quoted and unquoted values still expand escapes and substitutions as before.Parser updates in
mise-dotenvskip escape handling whenValueStyle::SingleQuotedduring quote scanning, expansion, andpush_escape. Expectations in the multiline loader test are adjusted accordingly, and coverage adds a unit test pluse2e/env/test_env_file_single_quotesfor UNC paths, trailing\, and literal\nvia[env] _.fileandMISE_ENV_FILE.Reviewed by Cursor Bugbot for commit 49d3aa6. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit