Describe the bug
marimo edit --sandbox fails when a script's inline metadata uses relative path sources in [tool.uv.sources]. The sandbox machinery calls uv export --script which returns paths relative to the script file, then writes them to a temp requirements file. When uv run --with-requirements <tempfile> processes that file, it resolves those paths as relative to the cwd, not the script's original directory. In the reasonably common scenario where you're not running marimo in the script's directory (e.g. marimo edit --sandbox path/to/notebook.py) then this will cause an error in path resolution.
FWIW, both editable and non-editable path sources are affected. uv export outputs paths like -e ../../ for editable sources and bare ../../ for non-editable ones.
Proposed fix
Convert relative paths to absolute paths in marimo/_cli/sandbox.py:_uv_export_script_requirements_txt(), using the script's parent directory as the base. This function has a single call site (_resolve_requirements_txt_lines) and AFAICT there's no scenario where it would be important to preserve relative paths.
def _uv_export_script_requirements_txt(
name: str | None,
) -> list[str]:
if not name:
return []
result = subprocess.run(
[
find_uv_bin(),
"export",
"--no-hashes",
"--no-annotate",
"--no-header",
"--script",
name,
],
check=True,
capture_output=True,
text=True,
)
- return result.stdout.split("\n")
+
+ lines = result.stdout.split("\n")
+
+ # uv export returns local paths relative to the script file, but
+ # these will be written to a temp requirements file and consumed
+ # by `uv run --with-requirements`, which resolves relative paths
+ # from CWD. Convert to absolute paths to avoid mismatch.
+ # Applies to both editable (-e ../../) and non-editable (../../)
+ # path sources.
+ script_dir = Path(name).resolve().parent
+ resolved = []
+ for line in lines:
+ editable = line.startswith("-e ")
+ path = line[3:].strip() if editable else line.strip()
+ if path.startswith("."):
+ path = str((script_dir / path).resolve())
+ prefix = "-e " if editable else ""
+ resolved.append(f"{prefix}{path}")
+ return resolved
I'm happy to submit a PR or someone can just implement the fix directly.
BTW, the above assumes all relative paths are prefixed with . (either ../[something] or ./[something]), which uv currently does. If that assumption feels dangerous, it could resolve any paths that don't start with /. Or it could even resolve everything, since pathlib will just return the second path when asked to join two absolute paths.
Will you submit a PR?
Environment
Details
{
"marimo": "0.22.0",
"editable": false,
"OS": "Darwin",
"OS Version": "24.6.0",
"Processor": "arm",
"Python Version": "3.13.7",
"Locale": "C/en_US",
"Binaries": {
"Browser": "146.0.7680.179",
"Node": "v25.6.1",
"uv": "0.10.2 (Homebrew 2026-02-10)"
},
"Dependencies": {
"click": "8.3.1",
"docutils": "0.22.4",
"itsdangerous": "2.2.0",
"jedi": "0.19.2",
"markdown": "3.10.2",
"narwhals": "2.18.1",
"packaging": "26.0",
"psutil": "7.2.2",
"pygments": "2.20.0",
"pymdown-extensions": "10.21.2",
"pyyaml": "6.0.3",
"starlette": "1.0.0",
"tomlkit": "0.14.0",
"typing-extensions": "4.15.0",
"uvicorn": "0.42.0",
"websockets": "16.0"
},
"Optional Dependencies": {
"loro": "1.10.3",
"polars": "1.39.3",
"pytest": "9.0.2",
"python-lsp-ruff": "2.3.0",
"python-lsp-server": "1.14.0",
"ruff": "0.15.8"
},
"Experimental Flags": {}
}
Code to reproduce
mkdir -p /tmp/repro/subdir/nested && cd /tmp/repro
cat > pyproject.toml << 'EOF'
[project]
name = "dummy-pkg"
version = "0.1.0"
requires-python = ">=3.13"
EOF
cat > subdir/nested/notebook.py << 'PYEOF'
# /// script
# requires-python = ">=3.13"
# dependencies = [
# "marimo>=0.22.0",
# "dummy-pkg",
# ]
#
# [tool.uv.sources]
# dummy-pkg = { path = "../../", editable = true }
# ///
import marimo
__generated_with = "0.22.0"
app = marimo.App()
@app.cell
def _():
print("hello")
if __name__ == "__main__":
app.run()
PYEOF
marimo edit --sandbox subdir/nested/notebook.py
Expected: sandbox resolves ../../ from the script's location (subdir/nested/), finding the root pyproject.toml.
Actual:
× Failed to resolve `--with` requirement
╰─▶ /private does not appear to be a Python project, as neither
`pyproject.toml` nor `setup.py` are present in the directory
../../ is resolved from CWD (/tmp/repro), landing at /private/tmp (macOS /tmp symlink) instead of the project root.
Describe the bug
marimo edit --sandboxfails when a script's inline metadata uses relativepathsources in[tool.uv.sources]. The sandbox machinery callsuv export --scriptwhich returns paths relative to the script file, then writes them to a temp requirements file. Whenuv run --with-requirements <tempfile>processes that file, it resolves those paths as relative to the cwd, not the script's original directory. In the reasonably common scenario where you're not running marimo in the script's directory (e.g.marimo edit --sandbox path/to/notebook.py) then this will cause an error in path resolution.FWIW, both editable and non-editable path sources are affected.
uv exportoutputs paths like-e ../../for editable sources and bare../../for non-editable ones.Proposed fix
Convert relative paths to absolute paths in
marimo/_cli/sandbox.py:_uv_export_script_requirements_txt(), using the script's parent directory as the base. This function has a single call site (_resolve_requirements_txt_lines) and AFAICT there's no scenario where it would be important to preserve relative paths.def _uv_export_script_requirements_txt( name: str | None, ) -> list[str]: if not name: return [] result = subprocess.run( [ find_uv_bin(), "export", "--no-hashes", "--no-annotate", "--no-header", "--script", name, ], check=True, capture_output=True, text=True, ) - return result.stdout.split("\n") + + lines = result.stdout.split("\n") + + # uv export returns local paths relative to the script file, but + # these will be written to a temp requirements file and consumed + # by `uv run --with-requirements`, which resolves relative paths + # from CWD. Convert to absolute paths to avoid mismatch. + # Applies to both editable (-e ../../) and non-editable (../../) + # path sources. + script_dir = Path(name).resolve().parent + resolved = [] + for line in lines: + editable = line.startswith("-e ") + path = line[3:].strip() if editable else line.strip() + if path.startswith("."): + path = str((script_dir / path).resolve()) + prefix = "-e " if editable else "" + resolved.append(f"{prefix}{path}") + return resolvedI'm happy to submit a PR or someone can just implement the fix directly.
BTW, the above assumes all relative paths are prefixed with
.(either../[something]or./[something]), whichuvcurrently does. If that assumption feels dangerous, it could resolve any paths that don't start with/. Or it could even resolve everything, sincepathlibwill just return the second path when asked to join two absolute paths.Will you submit a PR?
Environment
Details
Code to reproduce
Expected: sandbox resolves
../../from the script's location (subdir/nested/), finding the rootpyproject.toml.Actual:
../../is resolved from CWD (/tmp/repro), landing at/private/tmp(macOS/tmpsymlink) instead of the project root.