Skip to content

fix: name the agent binary devpod picked from a hostname (#560 section 3) - #562

Open
blooop wants to merge 1 commit into
mainfrom
fix/560-name-the-arm-agent-126
Open

fix: name the agent binary devpod picked from a hostname (#560 section 3)#562
blooop wants to merge 1 commit into
mainfrom
fix/560-name-the-arm-agent-126

Conversation

@blooop

@blooop blooop commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Section 3 of #560 — A warm attach reports neither a stale checkout nor a skipped setup pass, and an arm in a workspace id kills the launch with exit 126.

Stacked on #561 (sections 1 and 2), because that PR added the docs/cli.md troubleshooting section this one extends. Base retargets to main when #561 merges.

The bug

devpod chooses which agent binary to inject by globbing uname -a for arm. uname -a prints the nodename beside the machine, so any container whose hostname contains arm reads as an ARM machine: devpod downloads the arm64 agent, performVersionCheck cannot execute it, and the launch dies with exit status 126. That means "not executable", which is true and names neither the architecture nor the word that chose it.

dl is one of the ways the word gets there. The setup pass runs sudo hostname <workspace-id> in every container it opens, and a workspace id is derived from the branch — so feature/armature becomes devlaunch-feature-armature-17uu becomes a container that reads as ARM. alarm, warm, charm, swarm, harm and farm are the same trap.

What this adds

A refused devpod up of a workspace whose id contains arm gains one line beside devpod's own, with no change to the exit code:

If devpod said 'inject agent' and 'exit status 126' above, this is why: the workspace id
devlaunch-armature-cyi9 contains 'arm', devpod picks its agent binary by matching 'uname -a'
against '*arm*', and 'uname -a' includes the container's hostname — which dl sets to that id.
So devpod fetched the arm64 agent for a container on a host reporting x86_64, and could not
execute it. Rename the branch, or copy the right binary in with 'docker cp "$(command -v
devpod)" <container>:/usr/local/bin/devpod' and reconnect; a recreate wipes that.

Silent for every other refusal, for an id with no arm in it, and on a host whose own architecture reads as ARM — there devpod's guess is the right one and the failure is something else. Both of those questions go through one predicate, clients::devpod::reads_as_arm, since "would that glob match" is the same question asked of a hostname or of a machine.

Why the sentence is a conditional

Because dl never reads devpod's message, and that is worth keeping. The issue proposed matching devpod's stderr for inject agent together with exit status 126, citing clients/devpod.rs as already reading it. That reader is devpod::run_watching_stderr, and its only caller is flows/lifecycle/delete.rs. devpod up goes through devpod::run, a passthrough, deliberately: an image build's progress belongs on the user's terminal.

Switching the up over would cost two things, not one:

  1. The build's output stops going straight to the terminal. runner.session starts the child with Stdio::piped() stderr, so whatever devpod renders based on stderr being a tty changes, and every line goes through dl's eprintln! — for the several minutes an image build takes.
  2. session hardcodes OwnGroup::No, and the up needs Yes. up is the one call with leading_its_own_group(), which is what lets dl's interrupt handler killpg the build. Without it a Ctrl-C releases the launch lock while the build carries on holding it, which is the orphaned-build bug from #304 (divergence row 27).

So dl holds a nonzero exit and a workspace id. That is enough to know the trap is set and not enough to know it fired, and the line says so in those words rather than asserting what happened. An up that failed on an image pull for a workspace called alarm-clock gets one sentence it can discard on sight; that is the price of not piping the build, and it is the cheaper side of the trade.

This also means the issue's suggested negatives — a 126 that is not an agent injection, an inject agent failure that is not 126 — cannot be pinned as tests, because dl cannot see either. The conditional wording is what handles them instead, and a_failed_up_of_an_arm_named_workspace_names_the_trap_as_a_possibility asserts the line still opens with If devpod said for exactly that reason.

Tests

  • reads_as_arm in clients/devpod.rs: the substring behaviour on ordinary English branch names, the names that hold no arm, that it is not case-folded (a shell case is not, so a fold would answer a question devpod does not ask), and that the same predicate answers for aarch64/armv7l as for a hostname.
  • arm_agent_hint in dl/src/render.rs: the line's contents, absence on an ARM host, absence for an ordinary id, and absence for every other refusal.
  • a_failed_up_of_an_arm_named_workspace_names_the_agent_binary_devpod_picked in dl/tests/launch.rs: the whole line at the binary boundary, after devpod's own, with the exit code still devpod's 7. It follows the binary rather than assuming the runner — on an ARM host it asserts nothing was added.
  • an_ordinary_workspace_whose_up_failed_gets_no_arm_explanation: the negative that keeps this off every failed build.

New fixture --arm-branch in launch_scenario.py: a third branch on origin.git called armature, with no clone and no record, so dl blooop/devlaunch@armature is a cold launch whose derived id contains arm. The branch name is an ordinary English word on purpose.

LaunchRefusal::UpRefused grew a workspace_id. It is for the renderer alone — the exit code is still exit — and its doc comment says so.

Testing

cargo test --workspace, cargo clippy --locked --all-targets -- -D warnings, cargo fmt --check, pytest test/ (707) clean, with two exceptions worth naming:

  • flows::provision::tests::the_probe_reports_the_config_facts_when_it_actually_runs fails on main in this devcontainer too and is untouched here: it expects claudemounts empty and this container's own mount table answers /. It passed on CI's runners for fix: a warm launch says what it skipped (#560 sections 1 and 2) #561.
  • a_devpod_that_refuses_the_listing_is_exit_1_and_one_line failed once under a parallel --workspace run and did not reproduce in five repeats of its own test binary. Nothing in this change touches the listing path; noting it rather than claiming it is clean.

rust/devlaunch-core/public-api.rest.txt was hand-edited again (two rows, in sorted position). #561's public-api job confirmed the same method works, but CI's diff is still the real check.

Divergence row 35 records the line.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Dn4qJGhkW4KdwQnMuNSXsN

Summary by Sourcery

Explain likely agent-binary misselection when a failed devpod launch uses a workspace ID containing arm, without intercepting devpod's own output or changing its failure status.

Bug Fixes:

  • Add a targeted diagnostic for failed devpod up operations where workspace names containing arm may cause the wrong agent binary to be selected and produce exit status 126.

Enhancements:

  • Mirror devpod's ARM-detection predicate and use it to suppress misleading hints on ARM hosts or unrelated failures.
  • Preserve devpod's original exit status and passthrough diagnostics while carrying the workspace ID through launch refusals for rendering.

Documentation:

  • Document the conditional agent-selection diagnosis, recovery command, and recreate behavior in the CLI guide and changelog.
  • Record the change in the Rust rewrite divergence plan and public API snapshot.

Tests:

  • Add unit and integration coverage for ARM-name detection, conditional hint rendering, unaffected refusals, exit-status preservation, and ARM-host behavior.
  • Add an armature branch fixture for end-to-end launch scenarios.

@sourcery-ai sourcery-ai 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.

Sorry @blooop, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 1 day and 11 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR diagnoses devpod’s hostname-sensitive ARM agent selection without changing devpod or piping its up output: it mirrors the upstream predicate, carries the workspace ID through refused launches, conditionally appends a remediation hint on non-ARM hosts, and adds unit, integration, fixture, documentation, and API/divergence updates.

Sequence diagram for conditional ARM agent hint on failed devpod up

sequenceDiagram
    participant User
    participant Dl as dl
    participant Devpod
    participant Container
    participant Host

    User->>Dl: Launch workspace
    Dl->>Devpod: up(workspace)
    Devpod->>Container: Match uname -a against *arm*
    Container-->>Devpod: Host architecture and workspace hostname
    Devpod-->>Dl: Refused with exit status
    Dl->>Dl: reads_as_arm(workspace_id)
    Dl->>Dl: reads_as_arm(host_arch)
    alt workspace id matches and host is not ARM
        Dl-->>User: Conditional agent-binary hint
    else ordinary id or ARM host
        Dl-->>User: Preserve devpod diagnostics only
    end
    Dl-->>User: Return devpod exit code
Loading

Flow diagram for hostname-triggered ARM agent selection

flowchart LR
    Branch[Branch contains arm] --> Workspace[Workspace ID contains arm]
    Workspace --> Hostname[dl sets container hostname]
    Hostname --> Uname[uname -a includes hostname]
    Uname --> Match[devpod matches *arm*]
    Match --> Agent[devpod downloads arm64 agent]
    Agent --> Failure[Agent cannot execute: exit status 126]
    Failure --> Hint[dl suggests rename or docker cp on non-ARM host]
Loading

File-Level Changes

Change Details Files
Add a shared, case-sensitive predicate mirroring devpod’s ARM detection globs.
  • Match arm, arm64, aarch, and aarch64 as substrings.
  • Cover ordinary branch names, non-matches, case sensitivity, and architecture values with unit tests.
rust/devlaunch-core/src/clients/devpod.rs
Propagate the workspace ID through refused devpod up outcomes without changing process status handling.
  • Extend LaunchRefusal::UpRefused with renderer-only workspace metadata.
  • Populate it on both cold and warm launch paths while preserving devpod’s exit code.
rust/devlaunch-core/src/flows/launch.rs
rust/dl/src/launch.rs
rust/dl/src/render.rs
Emit a conditional diagnostic when an x86 host and ARM-like workspace name indicate devpod may have selected the wrong agent.
  • Render the hint only for refused up operations whose workspace ID matches the ARM predicate and whose host architecture does not.
  • Explain the uname -a hostname match, expected exit status 126, and recovery via docker cp or recreation.
  • Print the hint after devpod’s passthrough diagnostics and test its content, exclusions, ordering, and unchanged exit code.
rust/dl/src/render.rs
rust/dl/tests/launch.rs
Document the hostname-based agent-selection failure and the intentional conditional wording.
  • Update CLI troubleshooting guidance and the changelog with diagnosis and remediation.
  • Record the implementation and rationale in divergence row 35 and update the public API snapshot.
  • Add an armature branch fixture for cold-launch integration tests.
docs/cli.md
CHANGELOG.md
docs/rust-rewrite-plan.md
rust/devlaunch-core/public-api.rest.txt
rust/dl/tests/launch_scenario.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.39640% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.90%. Comparing base (b5d0300) to head (ee6ba5d).

Files with missing lines Patch % Lines
rust/devlaunch-core/src/flows/launch.rs 55.55% 4 Missing ⚠️
Additional details and impacted files
Flag Coverage Δ
python 42.98% <ø> (ø)
rust 95.17% <96.39%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
shipped code (rust) 95.17% <96.39%> (+<0.01%) ⬆️
harness and tooling (python) 42.98% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Base automatically changed from fix/560-a-warm-launch-says-what-it-skipped to main September 4, 2026 12:55
Section 3 of #560, as far as dl can honestly reach it.

devpod chooses which agent binary to inject by globbing `uname -a` for `arm`,
and `uname -a` prints the nodename beside the machine. So a container whose
hostname holds `arm` reads as an ARM machine, devpod downloads the arm64 agent,
the version check cannot execute it, and the launch dies with `exit status
126` -- "not executable", which names neither the architecture nor the word
that chose it. dl is one of the ways the word gets there: its setup pass sets
the container's hostname to the workspace id, and a workspace id is derived
from the branch, so `feature/armature` is enough.

A refused `devpod up` of a workspace whose id contains `arm` now carries one
line of dl's own, beside devpod's sentence and without changing the exit code.

The line is a conditional, and that is the honest limit rather than hedging.
dl runs the `up` as a passthrough, because an image build's progress belongs on
the user's terminal rather than through a pipe, so dl never reads devpod's
message: what it holds is a nonzero exit and a name. That is enough to know the
trap is set and not enough to know it fired, so the sentence says what to look
for in devpod's own output above it. Reading that output instead would mean
piping the build through dl -- changing what devpod renders, and dropping the
process group that lets a Ctrl-C tear a build down rather than orphan it
holding the launch lock, since `Runner::session` starts its child with
`OwnGroup::No` where the `up` needs `Yes` (#304).

`clients::devpod::reads_as_arm` transcribes devpod's four globs, uncase-folded
because a shell `case` is, and answers for a machine name as readily as for a
hostname -- which is what the exemption reads: a host that is itself ARM gets
no line, because there devpod's guess is right.

The upstream fix is one line (glob `uname -m`) and is not here.

Claude-Session: https://claude.ai/code/session_01Dn4qJGhkW4KdwQnMuNSXsN
@blooop
blooop force-pushed the fix/560-name-the-arm-agent-126 branch from de863a5 to ee6ba5d Compare September 4, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant