Skip to content

relay 0.5.0

Choose a tag to compare

@HarperZ9 HarperZ9 released this 27 Sep 13:50
· 22 commits to main since this release
ba1e4f2

0.5.0, 2026-09-27

A program planted in a project folder no longer runs through a PATH entry that
reaches that folder. 0.4.0 started its children through safe_spawn 1.0.0,
which skipped only relative PATH entries. This release vendors safe_spawn
1.0.1 and hands it the folder each child works in.

This was prepared as a 0.4.1 patch. It takes the minor number instead because
it changes what existing setups do, the rule 0.3.0 set for a pre-1.0 break: a
run, test_cmd or check command, or a git hook or filter during
--auto-commit, no longer finds a program through a project folder on PATH,
such as .venv/bin or node_modules/.bin, unless that folder holds the
interpreter relay runs on. A pin such as ~=0.4.0 excludes this release;
widen it to take the fix. The MCP tool names, the relay.mcp-run-request/v2
binding and the shapes of relay.status and relay.doctor are unchanged.

Source version metadata is not release availability proof; release availability
is established only by the accepted Git tag, uploaded GitHub Release assets, and
matching hash readback. Do not publish or recommend the bare PyPI name
relay-agent; that public namespace belongs to an unrelated project and is not
the HarperZ9 Relay distribution.

Security

  • An absolute PATH entry could reach a folder a child works in: the server's
    folder, the root of a run, test_cmd or check, the project a bisect
    check copies, or the repository --auto-commit commits to. The entry could
    name the folder or a folder below it, spell it another way, or lead there
    through a junction or symlink. On Windows a quoted entry such as
    "C:\proj"\bin reached it too, because cmd.exe drops the quotes. A program
    planted there ran in place of the installed claude, codex or git,
    including the --version check that relay.doctor starts under exec, and a
    shell child found it by bare name. Affected: every release through 0.4.0.
    0.4.0 closed the direct search of the server's folder and left these routes
    open.
  • git itself ran with that PATH, so a program git starts by bare name ran
    from the repository: a clean filter the repository selects in
    .gitattributes, or gpg when commits are signed. Affected: every release
    through 0.4.0.
  • On Windows a drive-relative name such as C:claude named a file in the
    current folder of drive C:. relay's own tiers use fixed names, so this needed
    a caller that builds a CliBackend with such a name. It is now refused with
    BAD_PATH.

Changed

  • The vendored safe_spawn is 1.0.1 (src/relay/_vendor/safe_spawn.py,
    SHA-256 recorded in VENDORED.sha256). It drops every PATH entry that
    reaches the server's folder or the folder named for the child, from the
    lookup and from the child's PATH. Folders are compared by name and by file
    identity after links are resolved.
  • run, test_cmd and check hand their root to the helper. A bisect check
    runs in a fresh copy of the project, so it hands the helper the project it
    copied.
  • --auto-commit resolves git with the repository as the named folder, and
    starts it with a PATH guarded the same way. git keeps the rest of its
    environment (HOME, GNUPGHOME, SSH_AUTH_SOCK, its GIT_ variables). One
    lookup serves every git call of a commit.
  • A command in run, test_cmd or check that found a program through a
    folder inside the root or the server's folder, such as a project's
    .venv/bin or node_modules/.bin, now needs that program's path
    (.venv/bin/pytest). So does a git hook or filter that found a program that
    way during --auto-commit. The folder that holds the interpreter relay runs
    on stays on PATH, so starting relay from the project's virtual environment
    keeps that environment's tools (.venv/bin, or .venv\Scripts on Windows).
    Tools in other folders inside the project, such as a Windows conda
    environment's Scripts and Library\bin, need their path. On Windows the
    Windows, System32 and SysWOW64 folders also stay.
  • A child's PATH names each kept folder as its real folder, with links
    resolved, so a link repointed after the check cannot change what starts. On
    merged-/usr Linux, /bin reaches a child as /usr/bin.
  • On Windows, PATH is read the way cmd.exe reads it, quotes included. An entry
    whose folder name holds ; is left out, since Windows programs disagree on
    how to read it.
  • On POSIX, a PATH that the filter leaves empty reaches the child as
    /bin:/usr/bin, because an empty PATH means the current folder there.

Limits

  • When the server's folder or a run's root is a filesystem root, or holds the
    home folder, only an entry naming that folder itself leaves. The tool folders
    below home, such as ~/.local/bin, stay.
  • The check reads the filesystem before the start. Swapping the program file,
    or a folder inside a kept folder, between the two still wins. That needs write
    access to a folder PATH already trusts.
  • On a filesystem without file indices, such as some network shares, every
    PATH entry on the working folder's device leaves. A CLI there needs
    RELAY_CLAUDE_CLI or RELAY_CODEX_CLI, and a shell tool there needs its
    path.
  • Each start reads every PATH entry, a step that took under 1 ms on 0.4.0.
    On Windows, with 58 entries, one read took about 11 ms. Under WSL with the
    Windows PATH appended (67 entries, 58 of them under /mnt) it took 0.5 to
    1.3 s, and under 1 ms with the /mnt entries removed. Every shell child and
    every CLI tier start pays at least one read. An --auto-commit reads PATH
    once for all its git calls, about 1.3 s there against 12 ms on 0.4.0.
  • The new tests ran on Windows 11 and on Linux (Ubuntu 24.04 under WSL2).
    macOS was not run.