From 0463d4566034febf8c4b1f0cb51be6af0a207c8d Mon Sep 17 00:00:00 2001 From: Francois Lanusse Date: Fri, 21 Aug 2026 11:29:39 +0200 Subject: [PATCH 1/2] Eval trace follow-up: document lc run's argv convention, clean the env MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PR-190 eval trace showed three harness-attributable frictions. lc run already follows the ecosystem convention — argv, no shell parsing, exactly docker run and uv run (verified: uv run "python -V" fails the same way) — so the behavior stands and the syntax gets one line where it was missing: the eval prompt and the run reference both now say argv style, never a single quoted string, bash -c for shell syntax. That one line was the difference between the agent's first probe working and a cryptic /usr/bin/env error plus a misleading sandbox trailer. The harness environment gets the same treatment VIRTUAL_ENV got: setup-uv's python-version input exported an ambient UV_PYTHON, which the install-settings scrub then rightly warned about on every single lc invocation — a dozen unactionable lines the agent ended up grep -v'ing away. The input is gone and the interpreter is pinned per tool install instead. And the checkout fetches full history, so hatch-vcs stamps a truthful dev version instead of 0.1.dev1 into the installed engine and every manifest it writes. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DJzmp2MUhwiNHR94cB91dx --- .github/workflows/eval.yml | 14 +++++++++++--- docs/cli/run.md | 5 ++++- evals/prompt.md | 5 ++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/eval.yml b/.github/workflows/eval.yml index 4befd2b..949205c 100644 --- a/.github/workflows/eval.yml +++ b/.github/workflows/eval.yml @@ -31,11 +31,19 @@ jobs: timeout-minutes: 20 steps: - uses: actions/checkout@v4 + with: + # Full history so hatch-vcs sees the tags and stamps a truthful + # dev version into the installed engine (and every manifest's + # lc_version) instead of a 0.1.dev fallback + fetch-depth: 0 - name: Set up uv + # No python-version input: it would export an ambient UV_PYTHON, + # which lc's install-settings scrub then rightly warns about on + # every single invocation the agent makes. The interpreter is + # pinned per tool install below instead. uses: astral-sh/setup-uv@v6 with: - python-version: "3.12" enable-cache: true - name: Install lightcone-cli + astra (uv tools) @@ -49,8 +57,8 @@ jobs: # venv is activated: the agent's shell sees the tools exactly as # an end user's would. run: | - uv tool install "$GITHUB_WORKSPACE" - uv tool install "astra-tools==$(grep -oP 'astra-tools==\K[0-9][0-9.]*' pyproject.toml)" + uv tool install --python 3.12 "$GITHUB_WORKSPACE" + uv tool install --python 3.12 "astra-tools==$(grep -oP 'astra-tools==\K[0-9][0-9.]*' pyproject.toml)" echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Install Claude Code + astra plugin diff --git a/docs/cli/run.md b/docs/cli/run.md index 594a18a..2171b98 100644 --- a/docs/cli/run.md +++ b/docs/cli/run.md @@ -13,7 +13,10 @@ lc run COMMAND... ``` Everything after `run` is the command, verbatim — flags included. -`lc run` takes no options of its own, so nothing needs escaping: +Argv, the `docker run` / `uv run` convention: a single quoted string +would be exec'd as one filename, so probe shell syntax through +`bash -c` instead. `lc run` takes no options of its own, so nothing +else needs escaping: ```bash lc run python -c "import numpy; print(numpy.__version__)" diff --git a/evals/prompt.md b/evals/prompt.md index c0f948f..aa05abc 100644 --- a/evals/prompt.md +++ b/evals/prompt.md @@ -26,7 +26,10 @@ This project is driven by two CLIs — use them rather than improvising: making. - `lc run ` runs an ad-hoc command in the project environment under the same isolation a recipe gets — useful for - probing why a recipe would fail. + probing why a recipe would fail. Argv style, like `docker run` or + `uv run`: `lc run python scripts/fit.py --output /tmp/x`, never a + single quoted shell string; for shell syntax use + `lc run bash -c '...'`. - Outputs land in `results/baseline//`, each with a `.lightcone-manifest.json` manifest written and committed by the engine. Never write into `results/` yourself: a hand-placed file From 2aea17b87367db3d6e6df1c8bffaac0779de5764 Mon Sep 17 00:00:00 2001 From: Francois Lanusse Date: Fri, 21 Aug 2026 12:16:16 +0200 Subject: [PATCH 2/2] Escape the status header prose too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The crate line arrived with the hardening pass, in parallel with the escape fix, so it never met it: the engine's 'declare [project].license to enable it' rendered as 'declare .license' — rich reading the brackets as a style tag, swallowing the one word that names the fix. The sandbox header gets the same escape; both are engine prose, which the rendering rule already says is data, never markup. Regression test beside the other header assertions. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DJzmp2MUhwiNHR94cB91dx --- src/lightcone/cli/commands.py | 4 ++-- tests/test_cli.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/lightcone/cli/commands.py b/src/lightcone/cli/commands.py index 75319b2..8fbf4ca 100644 --- a/src/lightcone/cli/commands.py +++ b/src/lightcone/cli/commands.py @@ -368,8 +368,8 @@ def status(as_json: bool) -> None: "unfetched": "content not in this clone — the next build or run fetches it", }[state] lines.append(f" image: {tag} — {described}") - lines.append(f" sandbox: {report.sandbox}") - lines.append(f" crate: {report.crate}") + lines.append(f" sandbox: {escape(report.sandbox)}") + lines.append(f" crate: {escape(report.crate)}") lines.append("") marks = {"current": "[dim]·[/dim]", "behind": "[cyan]·[/cyan]", "stale": "[yellow]![/yellow]"} width = max((len(o.output) for o in report.outputs), default=0) diff --git a/tests/test_cli.py b/tests/test_cli.py index 8970165..1f5bd9e 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -561,6 +561,23 @@ def test_status_headers_answer_mode_image_and_sandbox( assert "crate: up to date with the outputs" in output +def test_status_header_prose_is_not_markup( + runner: CliRunner, project: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """The crate line names `[project].license`, and rich would read the + brackets as a style tag and swallow the one word that names the fix.""" + from lightcone.engine.materialize import StatusReport + + report = _report() + assert isinstance(report, StatusReport) + report.crate = "not maintained — declare [project].license to enable it" + _status_stub(monkeypatch, report) + + output = runner.invoke(main, ["status"]).output + + assert "declare [project].license to enable it" in output + + def test_build_on_a_direct_project_is_an_explanatory_no_op( runner: CliRunner, project: Path ) -> None: