Skip to content

fix(env): keep backslashes literal in single-quoted dotenv values - #14228

Merged
jdx merged 1 commit into
mainfrom
claude/github-issue-14227-900127
Oct 9, 2026
Merged

jdx merged 1 commit into
mainfrom
claude/github-issue-14227-900127

Conversation

@jdx

@jdx jdx commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

https://entire.io/gh/jdx/mise/trails/612

Since 2026.10.3, mise has processed backslash escapes inside single-quoted .env values. That broke Windows paths loaded through [env] _.file and the env_file setting:

# .env
UNC_PATH='\\fileserver\data\share'
DIR='C:\temp\'
2026.10.3–2026.10.6 Now (and 2026.10.2 and earlier)
UNC_PATH \fileserver\data\share. On Windows this resolves to D:\fileserver\... on the current drive \\fileserver\data\share
DIR parse error: unterminated quote C:\temp\

Single-quoted values are literal again, as they are in shell, Node's dotenv, and the dotenvy parser 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-dotenv unit test and e2e/env/test_env_file_single_quotes (both fail without the fix); cargo test -p mise-dotenv and the existing .env e2e 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 .env values: backslashes are no longer interpreted as escapes (including \' before the closing quote), matching shell, Node dotenv, and pre-2026.10.3 mise behavior. Double-quoted and unquoted values still expand escapes and substitutions as before.

Parser updates in mise-dotenv skip escape handling when ValueStyle::SingleQuoted during quote scanning, expansion, and push_escape. Expectations in the multiline loader test are adjusted accordingly, and coverage adds a unit test plus e2e/env/test_env_file_single_quotes for UNC paths, trailing \, and literal \n via [env] _.file and MISE_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

  • Bug Fixes
    • Single-quoted values in environment files now preserve backslashes as literal characters, including in paths and text. Backslash escaping continues to work in double-quoted and unquoted values.

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
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: edb6ccef-3c03-44bb-9848-f34587b4dd95

📥 Commits

Reviewing files that changed from the base of the PR and between e53d613 and 49d3aa6.


📒 Files selected for processing (3)
  • crates/mise-dotenv/src/lib.rs
  • crates/mise-dotenv/src/parse.rs
  • e2e/env/test_env_file_single_quotes

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.



📝 Walkthrough

Walkthrough

Single-quoted dotenv values now preserve backslashes literally. Unit and end-to-end tests cover Windows paths, literal \n text, and values loaded through configured env files.

Changes

Single-quoted dotenv parsing

Layer / File(s) Summary
Preserve backslashes in single-quoted values
crates/mise-dotenv/src/parse.rs, crates/mise-dotenv/src/lib.rs, e2e/env/test_env_file_single_quotes
Single-quoted parsing no longer treats backslashes as escapes. Unit and end-to-end tests check literal backslashes, including in a UNC path loaded through an env file.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium


Merge Risk: ⚪ Minimal · up to 49d3a

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)

Check name Status Explanation Resolution
Docstring Coverage Warning 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… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: preserving literal backslashes in single-quoted dotenv values.
Linked Issues check Passed Issue #14227 requires single-quoted .env values loaded through [env] _.file to retain both leading backslashes in UNC paths. The parser now disables backslash escaping for `ValueStyle::SingleQuote…
Out of Scope Changes check Passed The changes are limited to the mise-dotenv parser, its related multiline test expectation, and focused unit and end-to-end tests. They preserve escape handling for double-quoted and unquoted values.…

Full details: Docstring Coverage

Explanation

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.)



  • Fix all pre-merge checks with AI
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jdx
jdx merged commit 2ee2128 into main Oct 9, 2026
54 of 55 checks passed
@jdx
jdx deleted the claude/github-issue-14227-900127 branch October 9, 2026 12:00
jdx added a commit that referenced this pull request Oct 9, 2026
…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>
jdx pushed a commit that referenced this pull request Oct 9, 2026
<!-- 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)
@github-actions

Copy link
Copy Markdown
Contributor

Instruction counts

benchmark trend instructions Δ wall (min) Δ
env ▄▄▄▅▄▁█ 27,846,447 → 27,990,921 +0.52% 19.61 → 19.70ms +0.48%
hook-env ▇▇▇▇▇▁█ 29,858,357 → 30,025,734 +0.56% 20.72 → 20.76ms +0.20%
ls ▄▆▄█▇▁█ 32,045,250 → 32,162,335 +0.37% 21.51 → 21.62ms +0.55%
registry ▄▂▃▃▃▁█ 40,197,284 → 40,290,307 +0.23% 17.67 → 17.87ms +1.09%
startup ▁▅█▇▄▁▆ 11,055,760 → 11,096,294 +0.37% 12.17 → 12.34ms +1.44%

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.

49d3aa6e5425 vs 6be3cbdc639a · measured on the runner, not pushed to the history.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression in 2026.10.3: UNC paths in _.file .env values lose one of their two leading backslashes

1 participant