fix(env): break NIX_CONFIG bootstrap-loop

Two independent bugs were compounding into a corrupted NIX_CONFIG that
broke every local image rebuild:

1. scripts/tests/code/run.sh non-idempotently appended
   "accept-flake-config = true" to whatever NIX_CONFIG it inherited.
   On the next source of .env (which carried the previous value back),
   the line accumulated -- "true\naccept-flake-config = true\n..." --
   until Nix rejected the Boolean. Fix: pipe through `awk '\!seen[$0]++'`
   to dedup lines while preserving order.

2. utils/env/writer.py silently re-encoded literal newlines as `\n`
   escape sequences inside double-quoted values. docker-compose's
   --env-file loader does not decode them and neither does bash
   `source`, so any multi-line value round-tripped into garbage on the
   next read. Fix: raise ValueError instead -- producers must collapse
   to a single line before handing off to the env-builder.

Test fixtures for the writer adjusted to assert the new raise
semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 4.7 committed 2026-05-21 03:10:14 +02:00
1 parent 5e0036dfa9
commit a3782bf8d3
3 files changed
+19 -25

No files matched your search

+2 -1
View File
@@ -12,7 +12,8 @@ NIX_CONFIG_EFFECTIVE="$(
printf "%s\n%s\n" \
"${NIX_CONFIG:-}" \
"accept-flake-config = true" |
sed -e "s/[[:space:]]\+$//" -e "/^$/d"
sed -e "s/[[:space:]]\+$//" -e "/^$/d" |
awk '!seen[$0]++'
)"
export NIX_CONFIG="${NIX_CONFIG_EFFECTIVE}"
+9 -14
View File
@@ -41,11 +41,13 @@ class TestQuoteForDotenv(unittest.TestCase):
def test_backslash_then_quote(self) -> None:
self.assertEqual(quote_for_dotenv('a\\"b'), '"a\\\\\\"b"')
def test_newline_is_escaped(self) -> None:
self.assertEqual(quote_for_dotenv("line1\nline2"), '"line1\\nline2"')
def test_newline_raises(self) -> None:
with self.assertRaises(ValueError):
quote_for_dotenv("line1\nline2")
def test_carriage_return_is_escaped(self) -> None:
self.assertEqual(quote_for_dotenv("a\rb"), '"a\\rb"')
def test_carriage_return_raises(self) -> None:
with self.assertRaises(ValueError):
quote_for_dotenv("a\rb")
def test_unicode_passes_through_quoted(self) -> None:
self.assertEqual(quote_for_dotenv("naïve"), '"naïve"')
@@ -117,18 +119,11 @@ class TestWriteDotenv(unittest.TestCase):
text = read_text(str(dest))
self.assertIn('K="with space"', text)
def test_multiline_value_escaped_to_single_line(self) -> None:
"""docker-compose's .env loader cannot read multi-line values;
the writer collapses \\n into a literal escape."""
def test_multiline_value_raises(self) -> None:
with TemporaryDirectory() as td:
dest = Path(td) / ".env"
write_dotenv(self._build({"K": "a\nb"}), dest)
text = read_text(str(dest))
# Verify no literal newline inside the K=... entry.
entry_line = next(
line for line in text.splitlines() if line.startswith("K=")
)
self.assertEqual(entry_line, 'K="a\\nb"')
with self.assertRaises(ValueError):
write_dotenv(self._build({"K": "a\nb"}), dest)
def test_trailing_newline(self) -> None:
with TemporaryDirectory() as td:
+8 -10
View File
@@ -25,16 +25,14 @@ _SAFE_VALUE_RE = re.compile(r"^[A-Za-z0-9_/.@:%+=-]+$")
def quote_for_dotenv(value: str) -> str:
if value != "" and _SAFE_VALUE_RE.match(value):
return value
escaped = (
value.replace("\\", "\\\\")
.replace('"', '\\"')
# Escape newlines so multi-line values (e.g. NIX_CONFIG) collapse
# into a single env-file line. docker-compose's .env loader does
# not support multi-line values, and the env-file schema lint
# would otherwise see each continuation line as a separate entry.
.replace("\r", "\\r")
.replace("\n", "\\n")
)
if "\n" in value or "\r" in value:
raise ValueError(
"Multi-line env values cannot be written to .env "
"(bash `source` and docker-compose `--env-file` both fail "
"to round-trip them). Collapse to a single line at the "
f"producer. Offending value: {value!r}"
)
escaped = value.replace("\\", "\\\\").replace('"', '\\"')
return f'"{escaped}"'