Skip to content

fix: pep 668 installer, non-utf-8 stdio, adapters missing from wheel#367

Merged
plind-junior merged 4 commits into
testfrom
fix/fresh-install-gaps
Jul 6, 2026
Merged

fix: pep 668 installer, non-utf-8 stdio, adapters missing from wheel#367
plind-junior merged 4 commits into
testfrom
fix/fresh-install-gaps

Conversation

@plind-junior

@plind-junior plind-junior commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

a fresh-machine walkthrough of the readme (new $HOME, stock /usr/bin/python3.12, an ISO-8859-1 locale) hit three bugs before reaching the happy path. all three are fixed here, each reproduced first and covered by a regression test where the harness can carry one.

first, the curl one-liner dead-ends on pep 668 hosts (debian 12+, ubuntu 23.04+, homebrew python): when pipx is missing, pip install --user pipx is refused and the installer bails to a sudo hint — on exactly the machines new users have, and against the readme's "never needs sudo" promise. install.sh now falls back to hosting pipx in a private venv under ~/.local/share/vouch/pipx-venv, symlinks it into ~/.local/bin so later shells can reach it, and carries on. verified end-to-end in a throwaway $HOME: refused user-site install → venv fallback → vouch installed and smoke-tested, no sudo.

second, the cli crashes on any non-utf-8 locale: python encodes stdio with the locale codec, so the / in vouch status and vouch search output raise UnicodeEncodeError and the cli is unusable. the cli entry now reconfigures stdio to utf-8 with errors="replace" (covering the mcp/jsonl servers, which flow through the same group), and the last locale-dependent file i/o sites — config reads in capture.py / themes.py and the migration rewriter's atomic_write_text — pin encoding="utf-8". the new test drives python -m vouch status in a subprocess with PYTHONIOENCODING=latin-1; it fails on the previous code, passes here, and needs no generated locales on ci.

third, and the big one for installed copies: vouch install-mcp <host> is broken on every pip/pipx install, including the published 1.1.0 wheel — adapters/ was resolved relative to the repo root, which only exists in checkouts, so the command fails with "unknown adapter 'claude-code' (available: (none))". the wheel now force-includes adapters/ at vouch/adapters/ and ADAPTERS_DIR falls back to the packaged copy; checkouts and editable installs keep preferring the repo directory so dev edits win. a regression test builds the wheel with the real backend (hatchling, added to the dev extras) and asserts the claude-code manifest is inside — it fails against the old pyproject.

verified beyond the unit gate: python -m build (the release.yml path, sdist → wheel-from-sdist) carries all 61 adapter files with no junk swept in; a pipx install from the patched tree ran the full readme quick start under the latin-1 locale — init, status, search, jsonl propose/approve/reject, export/import round-trip, install-mcp claude-code, and an mcp initialize handshake listing all 58 tools.

no behavior change for utf-8 environments: the reconfigure is a no-op when stdio is already utf-8, and repo checkouts resolve adapters exactly as before.

Summary by CodeRabbit

  • New Features

    • Improved installer behavior on systems that restrict user-level package installs, with an automatic fallback that keeps setup moving.
    • CLI output now handles non-UTF-8 environments more reliably.
  • Bug Fixes

    • Reduced encoding-related failures when reading configuration and writing files.
    • Ensured packaged builds include required adapter assets.
  • Tests

    • Added regression coverage for non-UTF-8 CLI execution.
    • Added packaging verification for wheel contents.

debian 12+ and ubuntu 23.04+ mark the system interpreter externally
managed, so the installer's `pip install --user pipx` fallback is
refused and the curl one-liner dead-ends on exactly the machines new
users have — while the readme promises "never needs sudo".

when the user-site install is refused, host pipx in a private venv
under ~/.local/share/vouch/pipx-venv, symlink it into ~/.local/bin so
later shells can still reach it, and continue. the sudo hint remains
as the last resort (now also mentioning python3-venv, which the venv
fallback needs on stock debian).
under LANG=en_US.ISO-8859-1 python encodes stdio with the locale
codec, so the '•' and '…' glyphs in `vouch status` / `vouch search`
output raise UnicodeEncodeError and the cli is unusable. kb artifacts
are utf-8 on disk; the wire should speak utf-8 too.

reconfigure stdin/stdout/stderr to utf-8 (errors="replace") at the
cli group entry — this also covers the mcp/jsonl servers, which flow
through the same entry point. terminals that genuinely can't render
a glyph now see a replacement character instead of a traceback.

also pin encoding="utf-8" on the last locale-dependent file i/o:
config reads in capture.py and themes.py, and the migration
rewriter's atomic_write_text.

the regression test drives `python -m vouch status` in a subprocess
with PYTHONIOENCODING=latin-1, which reproduces the failure without
needing the host to have any non-utf-8 locale generated.
`vouch install-mcp <host>` resolved adapters/ relative to the repo
root, which only exists in source checkouts. every pip/pipx install —
including the published 1.1.0 wheel — fails with "unknown adapter
'claude-code' (available: (none))", so the readme's documented path
to session auto-capture is broken for installed copies.

force-include adapters/ into the wheel at vouch/adapters/ and fall
back to that packaged copy when the repo-root directory is absent.
checkouts and editable installs keep preferring the repo copy so dev
edits win over a stale packaged snapshot.

the regression test builds a wheel with the real backend (hatchling,
now a dev dependency) and asserts the claude-code manifest is inside;
it fails against the previous pyproject wheel config.
@plind-junior plind-junior changed the base branch from main to test July 6, 2026 03:03
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds explicit UTF-8 encoding to config/file reads and writes, forces CLI stdio streams to UTF-8 with error replacement, introduces a pipx installation fallback to a private venv for PEP 668 externally-managed systems, and packages the adapters directory into built wheels, with associated tests.

Changes

UTF-8 Encoding, pipx Fallback, and Wheel Packaging

Layer / File(s) Summary
Explicit UTF-8 for config/file I/O
src/vouch/capture.py, src/vouch/themes.py, src/vouch/migrations/rewriter.py
Config reads and atomic file writes now specify encoding="utf-8" explicitly instead of relying on default encoding.
CLI stdio UTF-8 enforcement
src/vouch/cli.py, tests/test_cli.py
Adds _force_utf8_stdio() to reconfigure stdin/stdout/stderr to UTF-8 with error replacement at CLI startup; a new subprocess test verifies the status command works under a pinned non-UTF-8 environment encoding without UnicodeEncodeError.
pipx install fallback for PEP 668 systems
install.sh
Documentation and ensure_pipx logic updated so, on refused user-scope pip install, a private venv is created, pipx installed within it, PATH updated, and a shim symlinked into ~/.local/bin.
Wheel packaging of adapters
pyproject.toml, tests/test_install_adapter.py
Adds hatchling>=1.21 dev dependency and force-include config so the adapters directory ships in built wheels; a new test builds a wheel and asserts adapter templates are present.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as cli()
  participant Helper as _force_utf8_stdio
  participant Streams as sys.stdin/stdout/stderr

  User->>CLI: run vouch command
  CLI->>Helper: call _force_utf8_stdio()
  Helper->>Streams: check TextIOWrapper encoding
  Helper->>Streams: reconfigure(encoding="utf-8", errors="replace")
  CLI-->>User: continue command execution
Loading
sequenceDiagram
  participant Installer as install.sh
  participant Pip as pip install --user
  participant Venv as private venv
  participant LocalBin as ~/.local/bin

  Installer->>Pip: install --user --upgrade pipx
  Pip-->>Installer: refused (PEP 668)
  Installer->>Venv: create venv, install pipx
  Installer->>LocalBin: symlink pipx shim
  Installer->>Installer: pipx ensurepath, update PATH
Loading

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main fixes: PEP 668 installer fallback, non-UTF-8 stdio handling, and adapters missing from wheels.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/fresh-install-gaps

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.

@github-actions github-actions Bot added cli command line interface adapters agent host adapters and install manifests storage kb storage, migrations, schemas, and proposals packaging packaging, build metadata, and make targets tests tests and fixtures size: S 50-199 changed non-doc lines labels Jul 6, 2026
@plind-junior plind-junior merged commit 833b91a into test Jul 6, 2026
14 of 16 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@install.sh`:
- Around line 167-189: The private pipx fallback in install.sh currently creates
app environments from the pipx venv interpreter instead of the system Python. In
the fallback block that sets up pipx_venv and later invokes pipx, export
PIPX_DEFAULT_PYTHON to "$py" before any pipx install/upgrade or ensurepath usage
so pipx anchors vouch to the intended system interpreter. Use the existing
pipx_venv setup and pipx calls as the anchor points when making this change.

In `@pyproject.toml`:
- Around line 75-80: Update the adapter template lookup in install_adapter.py so
ADAPTERS_DIR matches the packaged wheel layout instead of only the source
checkout path. The current path resolution from vouch/install_adapter.py walks
up to the repo root, which works in development but misses the bundled
vouch/adapters directory after installation. Adjust the ADAPTERS_DIR resolution
logic to prefer the installed package location while still allowing the checkout
override, and keep the install_mcp-related template copy flow working for both
pip/pipx and source runs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: af3b26de-b585-4817-b816-33a534880dd5

📥 Commits

Reviewing files that changed from the base of the PR and between 44cec80 and dbd9934.

📒 Files selected for processing (8)
  • install.sh
  • pyproject.toml
  • src/vouch/capture.py
  • src/vouch/cli.py
  • src/vouch/migrations/rewriter.py
  • src/vouch/themes.py
  • tests/test_cli.py
  • tests/test_install_adapter.py

Comment thread install.sh
Comment on lines +167 to 189
if "$py" -m pip install --user --upgrade pipx >/dev/null 2>&1; then
"$py" -m pipx ensurepath >/dev/null 2>&1 || true
else
# Debian 12+ / Ubuntu 23.04+ mark the system interpreter externally
# managed (PEP 668) and refuse --user installs. Host pipx in a
# private venv instead — still no sudo.
info "user-site install refused (PEP 668) — using a private venv"
pipx_venv="$HOME/.local/share/vouch/pipx-venv"
if ! "$py" -m venv "$pipx_venv" >/dev/null 2>&1 || \
! "$pipx_venv/bin/pip" install --quiet --upgrade pipx >/dev/null 2>&1; then
err "could not install pipx (user site refused, private venv failed)"
err "install it manually, then re-run:"
err " sudo apt install pipx python3-venv # Debian/Ubuntu"
err " brew install pipx # macOS"
return 1
fi
PATH="$pipx_venv/bin:$PATH"; export PATH
# keep pipx reachable in later shells too (~/.local/bin is what
# `pipx ensurepath` puts on PATH)
mkdir -p "$HOME/.local/bin"
ln -sf "$pipx_venv/bin/pipx" "$HOME/.local/bin/pipx"
pipx ensurepath >/dev/null 2>&1 || true
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

When pipx is installed inside a virtualenv, does pipx install use the venv interpreter as the default python for app venvs?

💡 Result:

When pipx is installed inside a virtual environment, it does not distinguish that environment from any other environment it might be running in [1]. By default, pipx uses the Python interpreter that is currently executing it (i.e., sys.executable) to create new virtual environments for installed applications [2][1]. If you run pipx from within a virtual environment, sys.executable points to the Python binary inside that specific virtual environment [1]. Consequently, pipx will use that same virtual environment's Python interpreter as the default for any new environments it creates [1]. This behavior can lead to issues if the virtual environment containing pipx is later removed, moved, or modified, as the dependent environments created by pipx will become broken or inaccessible [1]. To ensure pipx uses a specific Python interpreter instead of the one it is currently running in, you should explicitly set the PIPX_DEFAULT_PYTHON environment variable to the path of your desired base Python interpreter [3][4][5] or use the --python flag during installation [3][4][5]. Top results: [3][5][2][1]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant section and any other references to pipx_venv / pipx install.
git ls-files | rg '^install\.sh$|pipx_venv|pipx install|pipx ensurepath|PIPX_DEFAULT_PYTHON|PIPX_BIN_DIR' -n || true

echo
echo "---- install.sh excerpt ----"
nl -ba install.sh | sed -n '150,230p'

echo
echo "---- pipx-related references ----"
rg -n "pipx_venv|pipx ensurepath|PIPX_DEFAULT_PYTHON|PIPX_BIN_DIR|pipx install" install.sh

Repository: vouchdev/vouch

Length of output: 237


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

path = Path("install.sh")
lines = path.read_text().splitlines()

print("---- install.sh 150-230 with line numbers ----")
for i in range(149, 230):
    if i < len(lines):
        print(f"{i+1:4d}: {lines[i]}")

print("\n---- all pipx-related lines ----")
for i, line in enumerate(lines, 1):
    if "pipx" in line or "PIPX" in line or "vouch-venv" in line:
        print(f"{i:4d}: {line}")
PY

Repository: vouchdev/vouch

Length of output: 7027


Set PIPX_DEFAULT_PYTHON for the private pipx venv

pipx will create app envs from the interpreter it’s running under, so this fallback can tie vouch to ~/.local/share/vouch/pipx-venv. Export PIPX_DEFAULT_PYTHON="$py" before the pipx install/upgrade calls so the installed vouch env stays anchored to the system Python.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install.sh` around lines 167 - 189, The private pipx fallback in install.sh
currently creates app environments from the pipx venv interpreter instead of the
system Python. In the fallback block that sets up pipx_venv and later invokes
pipx, export PIPX_DEFAULT_PYTHON to "$py" before any pipx install/upgrade or
ensurepath usage so pipx anchors vouch to the intended system interpreter. Use
the existing pipx_venv setup and pipx calls as the anchor points when making
this change.

Comment thread pyproject.toml
Comment on lines +75 to +80
# `vouch install-mcp <host>` copies templates out of adapters/. Ship them
# inside the wheel so installed (pip/pipx) copies can find them; from a
# checkout the repo-root adapters/ still wins (see install_adapter.py).
[tool.hatch.build.targets.wheel.force-include]
"adapters" = "vouch/adapters"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the full ADAPTERS_DIR resolution logic in install_adapter.py
fd install_adapter.py --exec cat -n {}

Repository: vouchdev/vouch

Length of output: 38931


Align ADAPTERS_DIR with the wheel layout. pyproject.toml now bundles the templates into vouch/adapters/, but src/vouch/install_adapter.py still resolves three parents up from vouch/install_adapter.py, which only matches the source checkout layout. In an installed wheel, that path misses the packaged templates, so vouch install-mcp <host> still breaks for pip/pipx installs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` around lines 75 - 80, Update the adapter template lookup in
install_adapter.py so ADAPTERS_DIR matches the packaged wheel layout instead of
only the source checkout path. The current path resolution from
vouch/install_adapter.py walks up to the repo root, which works in development
but misses the bundled vouch/adapters directory after installation. Adjust the
ADAPTERS_DIR resolution logic to prefer the installed package location while
still allowing the checkout override, and keep the install_mcp-related template
copy flow working for both pip/pipx and source runs.

plind-junior added a commit that referenced this pull request Jul 6, 2026
fix: adapter resolution in wheels, eager-help crash, installer hardening (#367 follow-up)
jsdevninja pushed a commit to jsdevninja/vouch that referenced this pull request Jul 6, 2026
pr vouchdev#367 force-included adapters/ into the wheel but the resolver hunk
never made the branch: ADAPTERS_DIR still walked three parents up from
the module — the repo root in a checkout, but <prefix>/lib/pythonX.Y/
in an installed wheel. installed copies therefore *contained* the
templates yet still failed install-mcp with "(available: (none))".

prefer the repo-root directory when it exists (checkouts, editable
installs) and fall back to the packaged vouch/adapters/ otherwise.

the wheel-contents regression test could not catch this — a wheel can
carry the files while the resolver misses them — so a second test now
unpacks the built wheel, imports that copy with no repo checkout in
sight, and asserts available_adapters() finds claude-code.
jsdevninja pushed a commit to jsdevninja/vouch that referenced this pull request Jul 6, 2026
click renders --help and --version during argument parsing, before any
group callback runs — and the group docstring's em dash already
crashes a latin-1 stdout. so the callback-scoped _force_utf8_stdio()
from vouchdev#367 left the very first command a fresh user is likely to type
(`vouch --help`) raising UnicodeEncodeError. run it at module import
instead; it is idempotent and a no-op on utf-8 streams.

also widen capture.load_config's except: reading config.yaml with
pinned utf-8 can now raise UnicodeDecodeError on latin-1 bytes (e.g. a
hand-edited config on exactly the hosts this fix targets), which
escaped the (OSError, YAMLError) tuple and broke the documented
fall-back-to-defaults contract.

the stdio regression test is parametrized over `status` and `--help`;
the help case fails against the callback-scoped variant.
jsdevninja pushed a commit to jsdevninja/vouch that referenced this pull request Jul 6, 2026
three hardening fixes for the pep 668 fallback from vouchdev#367:

- probe ~/.local/bin *before* deciding pipx is missing: fresh
  non-login shells often lack it on PATH, so an installed pipx looked
  absent and a needless private venv shadowed it. the symlink into
  ~/.local/bin now also refuses to overwrite an existing entry.
- recreate the private venv from scratch on re-runs: brew pythons
  ship read-only activate scripts, so `python -m venv` over a previous
  run's env dies with EACCES.
- stop announcing every pip failure as "refused (PEP 668)" and stop
  discarding installer output: attempts log to $TMPDIR and the final
  error prints the last lines, so a missing python3-venv or a network
  outage is diagnosable from the terminal.
jsdevninja pushed a commit to jsdevninja/vouch that referenced this pull request Jul 6, 2026
the v1.2.0 container job failed at `pip install '.[web]'` with "Forced
include not found: /app/adapters": the wheel force-include from vouchdev#367
requires adapters/ in the source tree, but .dockerignore excluded it
and the dockerfile never copied it. copy it and keep it in the
context.

verified with a local docker build: the image builds, and
`vouch install-mcp claude-code` against a bind-mounted /data writes
all 12 artifacts from inside the container — the packaged-copy
resolver working in the image, not just on hosts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adapters agent host adapters and install manifests cli command line interface packaging packaging, build metadata, and make targets size: S 50-199 changed non-doc lines storage kb storage, migrations, schemas, and proposals tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant