Skip to content

write-manifest voila resolves a directory path without a trailing slash to the working directory #886

Closed

Description

@tdstein

🤖 Filed on behalf of @tdstein.

rsconnect write-manifest voila <dir> uses dirname(path) as the project directory. For a directory given without a trailing slash, such as proj, that is '', the working directory. This causes one of two problems:

  • If the working directory has no requirements.txt, the command fails even though proj/requirements.txt exists.
  • If the working directory has a requirements.txt, the command succeeds but writes proj/manifest.json with the working directory's requirements, not the project's. Nothing warns that the wrong dependencies were recorded.

proj/ (trailing slash) and proj/report.ipynb both work.

Steps to reproduce

With rsconnect-python 1.32.0:

mkdir -p proj
printf '{"cells":[],"metadata":{},"nbformat":4,"nbformat_minor":5}' > proj/report.ipynb
echo voila > proj/requirements.txt

rsconnect write-manifest voila proj
Checking arguments...                            [OK]
[ERROR]
Error: The requirements file 'requirements.txt' does not exist in ''.
Please create the file or specify a different file with --requirements-file.
To have the requirements file generated using pip freeze, pass --force-generate.

Then add an unrelated requirements.txt to the working directory:

echo pandas > requirements.txt
rsconnect write-manifest voila proj
md5 -q requirements.txt proj/requirements.txt
python -c "import json; print(json.load(open('proj/manifest.json'))['files']['requirements.txt'])"
Creating manifest.json...                        [OK]
2f19884f14598d8de5efae75e6d86a7c
9dec73da42aacc7d693d84bbacf07dbe
{'checksum': '2f19884f14598d8de5efae75e6d86a7c'}

The manifest's requirements.txt checksum matches ./requirements.txt (pandas), not proj/requirements.txt (voila).

Cause

write_manifest_voila in rsconnect/main.py computes the base directory with dirname alone:

base_dir = dirname(path)

deploy_voila in the same file handles a directory:

base_dir = path if isdir(path) else dirname(path)

The same base_dir also sets manifest_path for the --overwrite check, so that check looks at ./manifest.json instead of proj/manifest.json.

Suggested fix

Use base_dir = path if isdir(path) else dirname(path) in write_manifest_voila, as deploy_voila does, and add a test that passes a directory with no trailing slash.

Context

Found while updating the Posit Connect docs to use the Posit CLI (posit connect write-manifest voila), which mounts this command. Checked against main at b1f5ca2.

Activity

  1. added
    bugSomething isn't working
    ready-for-agentFully specified, ready for an autonomous coding agent
    on Oct 27, 2026
  2. tdstein commented on Oct 27, 2026

    @tdstein
    CollaboratorAuthor

    Codex Workpad

    Plan

    • 1. Reproduce the Voilà directory-path bug.
      • 1.1 Confirm requirements selection and overwrite behavior.
    • 2. Fix project-directory resolution and add regression coverage.
      • 2.1 Preserve notebook-file and trailing-slash behavior.
    • 3. Validate and publish the PR.
      • 3.1 Run focused checks and review the diff.
      • 3.2 Publish the PR and bind validation evidence to its head.

    Acceptance Criteria

    • A Voilà directory without a trailing slash uses its own requirements and manifest path.
    • Notebook-file and trailing-slash paths continue to work.
    • Existing project manifests are protected unless overwrite is requested.

    Validation

    • Command:
      • python -m pytest -q tests/test_write_manifest_voila.py tests/test_environment.py tests/test_bundle.py tests/test_main.py tests/test_write_manifest_pyproject.py
      • ruff format --check rsconnect/main.py tests/test_write_manifest_voila.py
      • ruff check rsconnect/main.py tests/test_write_manifest_voila.py
      • git diff --check HEAD^ HEAD
    • Commit: 1a009cf
    • Observed: 310 passed, 10 skipped; formatting, lint, and diff checks passed. Commands used the existing isolated test environment.

    Evidence

    Reproduction

    • Command:
      python - <<'PY'
      import hashlib
      import json
      from pathlib import Path
      from click.testing import CliRunner
      from rsconnect.main import cli
      
      runner = CliRunner()
      with runner.isolated_filesystem():
          project = Path('proj')
          project.mkdir()
          (project / 'report.ipynb').write_text('{"cells":[],"metadata":{},"nbformat":4,"nbformat_minor":5}')
          (project / 'requirements.txt').write_text('voila\n')
          result = runner.invoke(cli, ['write-manifest', 'voila', 'proj'])
          print('no parent requirements:', result.exit_code, result.output.strip())
          Path('requirements.txt').write_text('pandas\n')
          result = runner.invoke(cli, ['write-manifest', 'voila', 'proj'])
          manifest = json.loads((project / 'manifest.json').read_text())
          print('with parent requirements:', result.exit_code)
          print('manifest checksum:', manifest['files']['requirements.txt']['checksum'])
          print('parent checksum:', hashlib.md5(Path('requirements.txt').read_bytes()).hexdigest())
          print('project checksum:', hashlib.md5((project / 'requirements.txt').read_bytes()).hexdigest())
          result = runner.invoke(cli, ['write-manifest', 'voila', 'proj'])
          print('existing project manifest:', result.exit_code, result.output.strip())
      PY
    • Observed: Missing parent requirements caused failure; with parent requirements the manifest checksum matched the parent, and rerunning overwrote the project manifest without permission.
    • Run: isolated Click CLI reproduction, 2026-10-27.
    • Commit: b1f5ca2

    Notes

    • No repository AGENTS.md exists; reviewed CLAUDE.md.
    • Workpad creation tool failed repeatedly; created the workpad with GitHub CLI. The tool does not recognize this comment and reports viewerDidAuthor=false.
    • Regression tests failed on the original code and revealed that environment inspection also needed a directory-safe path; all 8 focused cases now pass.
    • Local commit 1a009cfeda4e22dd2d3806b328c4503e478673d8: pytest -q tests/test_write_manifest_voila.py tests/test_environment.py tests/test_bundle.py tests/test_main.py tests/test_write_manifest_pyproject.py passed (310 passed, 10 skipped). ruff format --check rsconnect/main.py tests/test_write_manifest_voila.py, ruff check rsconnect/main.py tests/test_write_manifest_voila.py, and git diff --check passed.
    • Earlier PR creation and workpad updates failed with host errors; publication now succeeded.
    • On resume, focused tests passed (8 passed), Ruff formatting and lint passed, and the committed diff remains clean at 1a009cfeda4e22dd2d3806b328c4503e478673d8.
    • Handoff: PR Fix Voilà manifest directory resolution #890 at 1a009cfeda4e22dd2d3806b328c4503e478673d8; 310 tests passed, 10 skipped, and Ruff and diff checks passed. No unresolved review threads.

    Confusions

    • None.
  3. self-assigned this
    on Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingready-for-agentFully specified, ready for an autonomous coding agent

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions