Skip to content

Build: clear error when devpod binary is missing #57

Closed

Description

@blooop

Question

Build: fail with a clear message when the devpod binary is missing.

run_devpod (dl.py:695-708) catches nothing — on a box without devpod, dl --ls prints a raw FileNotFoundError traceback. Catch the missing-binary case at the shell-out seam and emit a one-line actionable error (install hint), exit non-zero. Runtime deps devpod/git/gh are all assumed on PATH and only git-lfs is probed (workspace_clone.py:111) — cover git at the same seam if it falls out naturally, but devpod is the ticket.

Activity

  1. self-assigned this
    on Aug 7, 2026
  2. added a commit that references this issue on Aug 7, 2026
    62e5174
  3. blooop commented on Aug 7, 2026

    @blooop
    OwnerAuthor

    Answer

    Landed in #71. run_devpod is the only place the package spawns devpod, so the missing binary is translated there, caught once in main(), and reported as one stderr line with exit 127 — the shell's own "command not found" code, and dl otherwise propagates devpod's returncodes verbatim.

    Two modelling choices carried the work:

    It is not an OSError. FileNotFoundError is one, and dl.py has ~12 broad except OSError / except (RuntimeError, OSError) handlers. Letting it travel as an OSError meant the attach path reported Failed to create workspace: [Errno 2]... — a misleading message, which is barely better than the traceback. Verified by experiment: rebasing the exception on OSError makes the survival test fail with assert 1 == 127.

    It is not a returncode. A synthetic 127 in the CompletedProcess would send callers — all of which branch on returncode — down paths that assume devpod ran. An absent binary is a different kind of outcome from a command that ran and failed, so it stays out of that value domain.

    Verified against the real CLI with PATH=/nonexistent, capturing the three streams separately: --help and --version are rc 0 with empty stderr and provably never call subprocess.run; --ls, --repos, --completion-data, --update-cache are rc 127 with empty stdout, one stderr line, no traceback. Empty stdout matters because the completion machinery parses it. --update-cache now exits rather than overwriting a good completion cache with an empty one.

    Also checked by hand: none of the five except Exception sites in the package can swallow it (dl.py:920 sits after its run_devpod call, wrapping only clone cleanup; the rest are git/clone work in worktree/), and run_devpod passes no cwd, so FileNotFoundError there can only mean the executable — the message cannot lie about the cause.

    git — assessed, deliberately not done

    The ticket allowed covering git at the same seam "if it falls out naturally". It does not. git is spawned from ~20 sites across dl.py, repo_manager.py, branch_manager.py and workspace_clone.py, and — measured, not assumed — there is no traceback to fix: with git absent, dl owner/repo@main already prints one line and exits 1.

    Repository 'owner/repo': [Errno 2] No such file or directory: 'git'
    

    Cryptic, but a single line. Improving it means either a new exception threaded through three modules or a shutil.which("git") probe that covers only the clone path (dl <existing-ws> and dl ./path never touch git). That is its own slice. The completion paths catch OSError and degrade to empty lists, which is correct there.

    Incidental

    The metadata-hardening merge (#56) had landed with codespell red on main, which blocks every branch cut from it regardless of content. Fixed in the same PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions