Skip to content

feat: dl rmt removes the workspace this herdr tab is named after - #685

Merged
blooop merged 7 commits into
mainfrom
autorm
Oct 16, 2026
Merged

blooop merged 7 commits into
mainfrom
autorm

Conversation

@blooop

@blooop blooop commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds dl rmt: run it inside a herdr tab and it removes the workspace that tab is named after, then hangs up the tab the way rme does.

  • Reads HERDR_TAB_ID, asks herdr tab get <id> for the tab's label, and maps the label to exactly one workspace devpod lists (a listed id as is, else the metadata records whose label matches). Zero or two matches refuse, naming why.
  • Runs rm's guarded delete, so unpushed work refuses unless --force. Only a removal that completes hangs up the parent shell; a refusal leaves the tab open.
  • stop, rm, rme and the selector are unchanged.

The herdr tab get reply shape comes from herdr's published schema, not from a run against a real herdr (the dev container has none). herdr tab get "$HERDR_TAB_ID" on the host should show result.tab.label.

Review

Self-reviewed: fixes for the refusal hint (dl rmt --force, not dl <id> rmt --force), the "removing" wording printed before the guard decided, nohup saying rme:, stale comments, and tests for the two-match refusal, stale records, unanswered herdr and refused listings.

Test plan

  • cargo fmt --check, cargo clippy --locked --all-targets -D warnings: clean
  • cargo test --workspace: 3134 passed
  • pytest test/: 857 passed
  • prek run --from-ref origin/main --to-ref HEAD: passed

🤖 Generated with Claude Code

Summary by Sourcery

Add dl rmt for safely deleting the workspace associated with the current herdr tab and closing the tab only after the deletion succeeds.

New Features:

  • Add dl rmt to identify the workspace named by the current herdr tab, remove it, and close the tab after a successful deletion.

Bug Fixes:

  • Preserve safe deletion behavior by refusing ambiguous, missing, stale, unanswered, or guarded workspace resolutions without closing the tab.

Enhancements:

  • Support resolving tab labels from listed workspace IDs or matching workspace metadata while keeping existing rm, rme, stop, and selector behavior unchanged.
  • Improve removal and hangup messages so they reference the command the user actually ran.

Documentation:

  • Document dl rmt, its workspace resolution rules, safety guard, force option, and herdr requirements in the README, CLI guide, and changelog.

Tests:

  • Add unit and lifecycle coverage for tab label parsing, workspace resolution, ambiguity, stale records, herdr failures, deletion guards, forced deletion, and tab hangup behavior.

… closes the tab

A launch names its herdr tab `repo@branch`, and the name stays after the
session closes. `dl rmt`, typed in that tab, reads the name with
`herdr tab get $HERDR_TAB_ID`, turns it into the one workspace id devpod
still lists (the id itself, or the records' label), prints the id, and runs
the same guarded delete as `rm`. After a removal that worked it hangs up the
shell, as `rme` does, so the tab closes. No workspace name and no selector.

Claude-Session: https://claude.ai/code/session_018kqBWJQXnHEACr4nBxAMKF
…grammar refuses

The guard's way past is now the command as it was typed, so rmt is told
dl rmt --force and rm, rme and kill print the line they did before.

Claude-Session: https://claude.ai/code/session_018kqBWJQXnHEACr4nBxAMKF
The hangup's own lines (SIGHUP already ignored, no parent to hang up,
kill refused) hard-coded 'rme:'. after_the_command now takes the word
the line used: verb.word() for rm/rme, cli::REMOVE_THIS_TAB for rmt.

Claude-Session: https://claude.ai/code/session_018kqBWJQXnHEACr4nBxAMKF
Also pins the stale-record refusal against a record the listing really
rejects (the orphan fixture), the unanswered tab, the unreadable listing,
the delete the id-named tab ends in, and rmt --claude-profile.

Claude-Session: https://claude.ai/code/session_018kqBWJQXnHEACr4nBxAMKF
# Conflicts:
#	CHANGELOG.md
#	rust/devlaunch-core/public-api.rest.txt
#	rust/devlaunch-core/src/clients/herdr.rs
@sourcery-ai

sourcery-ai Bot commented Oct 16, 2026

Copy link
Copy Markdown

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 3 days and 2 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Oct 16, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds dl rmt, which obtains the current herdr tab label, resolves it only to a unique workspace still listed by devpod, applies the existing guarded removal logic, and hangs up the tab’s shell only after a successful deletion, with comprehensive documentation and safety-focused tests.

Sequence diagram for dl rmt workspace removal

sequenceDiagram
    participant User
    participant DL as dl
    participant Herdr
    participant Devpod
    participant Metadata
    participant Workspace
    participant Shell

    User->>DL: dl rmt [--force]
    DL->>Herdr: tab get HERDR_TAB_ID
    Herdr-->>DL: result.tab.label
    DL->>Devpod: list workspaces
    alt label is listed workspace id
        DL->>DL: select label
    else label matches recorded workspace
        DL->>Metadata: recorded_ids_labelled(label)
        Metadata-->>DL: listed matching ids
    end
    alt exactly one workspace
        DL->>Workspace: remove_addressed(id, force)
        alt guarded removal succeeds
            Workspace-->>DL: removed
            DL->>Shell: hang up parent shell
        else removal refused
            Workspace-->>DL: refusal
            DL-->>User: show refusal and keep tab open
        end
    else zero or multiple matches
        DL-->>User: show refusal and keep tab open
    end
Loading

Flow diagram for dl rmt workspace resolution

flowchart TD
    A[dl rmt] --> B{HERDR_TAB_ID set?}
    B -- No --> C[Refuse: outside herdr]
    B -- Yes --> D[herdr tab get HERDR_TAB_ID]
    D --> E{Tab label returned?}
    E -- No --> F[Refuse: tab unanswered]
    E -- Yes --> G[devpod list workspaces]
    G --> H{Label is listed workspace id?}
    H -- Yes --> I[Select that workspace]
    H -- No --> J[Match label against listed recorded workspaces]
    J --> K{Exactly one match?}
    K -- No --> L[Refuse: zero or multiple matches]
    K -- Yes --> I
    I --> M[Run guarded removal]
    M --> N{Removal succeeds?}
    N -- No --> O[Show refusal; keep tab open]
    N -- Yes --> P[Hang up parent shell]
Loading

File-Level Changes

Change Details Files
Adds the rmt command and validates its command-line shape separately from workspace-targeted verbs.
  • Introduces dl rmt [--force] parsing and rejects workspace names, commands, and unrelated modifiers.
  • Reserves rmt as a command token and provides targeted grammar errors.
  • Preserves the existing rm guard and routes rmt through the removal lifecycle.
rust/dl/src/cli.rs
rust/dl/src/lib.rs
Determines the current herdr tab label and safely resolves it to exactly one currently listed workspace.
  • Reads HERDR_TAB_ID and invokes herdr tab get with a bounded response wait.
  • Parses the published tab-info response and distinguishes non-herdr, unanswered, and named-tab cases.
  • Resolves direct listed workspace IDs or matching metadata labels, filtering stale records and refusing zero or multiple matches.
rust/devlaunch-core/src/clients/herdr.rs
rust/devlaunch-core/src/flows/session_manager.rs
rust/devlaunch-core/src/flows/launch.rs
Implements tab-based removal using the existing guarded deletion behavior and closes the originating shell only after success.
  • Prints the resolved workspace before deletion and reuses the standard removal guard, including --force.
  • Leaves the tab open for lookup, listing, ambiguity, and guarded-removal refusals.
  • Reuses hangup handling for successful removal and updates messages to identify rmt correctly.
rust/dl/src/commands.rs
rust/dl/src/hangup.rs
rust/dl/src/render.rs
Documents and integration-tests the new tab-oriented workflow and its safety boundaries.
  • Adds CLI and lifecycle coverage for successful deletion, direct IDs, stale records, duplicate labels, missing herdr responses, unavailable listings, outside-herdr use, refusal, and force deletion.
  • Adds a forked-workspace fixture for ambiguity testing.
  • Documents usage, resolution rules, guard behavior, and hangup semantics.
CHANGELOG.md
README.md
docs/cli.md
rust/devlaunch-core/public-api.rest.txt
rust/dl/tests/lifecycle.rs
rust/dl/tests/lifecycle_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

@blooop blooop added the no-external-review Merge without an external review: a recorded decision, not a default label Oct 16, 2026
@codecov

codecov Bot commented Oct 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.16312% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.61%. Comparing base (a1f07d8) to head (221b926).

Files with missing lines Patch % Lines
rust/dl/src/render.rs 86.66% 4 Missing ⚠️
rust/dl/src/hangup.rs 66.66% 2 Missing ⚠️
rust/dl/src/commands.rs 98.24% 1 Missing ⚠️
rust/dl/src/lib.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
Flag Coverage Δ
python 42.98% <ø> (ø)
rust 94.82% <97.16%> (+0.03%) ⬆️

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

Components Coverage Δ
shipped code (rust) 94.82% <97.16%> (+0.03%) ⬆️
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.

@blooop
blooop merged commit 887a9e6 into main Oct 16, 2026
21 of 23 checks passed
@blooop
blooop deleted the autorm branch October 16, 2026 16:39
@blooop blooop mentioned this pull request Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-external-review Merge without an external review: a recorded decision, not a default

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant