Repository navigation
Conversation
flows::refresh_stale::plan reads, per stale workspace, its launch lock, one docker exec of its process table, and the Claude sessions herdr's panes hold, and says Refresh or Skip with the reason. A build process, a shell under a Claude process, a busy or unresumable agent, a Claude session no pane holds, and a herdr that cannot be seen are each a skip. StaleImage now carries the container it was judged by, which is the container the plan reads. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
LaunchVerb::RecreateUnattended is recreate's path, agent sessions included, ending at Launched::Ready instead of a session. It is what dl --refresh-stale runs for each workspace, with nobody to attach. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
… seen dl --refresh-stale recreates outside herdr only a container it read no Claude process in, so the warning that the recreate ends the agents it cannot see was false there. A recreate typed by hand still warns. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
It prints one line per stale workspace (refresh, or skip and why), then runs the unattended recreate for each it may refresh, says each launch's notices as they happen, and ends with a count. A recreate that fails is counted and the loop goes on; the exit status is 1 when any failed. Named in dl.bash's global options and in the help's examples. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
…review/refresh-stale # Conflicts: # CHANGELOG.md # rust/devlaunch-core/src/flows/launch.rs
…sed a Claude outside herdr beside a held codex The rule knew only Claude processes. A codex running a command, or any codex at all outside herdr, left no Claude process to find, so the workspace was refreshed and the codex ended. And the count compared Claude processes against herdr's panes of every agent kind, so one held codex covered a Claude started outside herdr, which then ended with no line to start it again. An agent is now any agent dl knows by name (herdr::agent_named), found the way Claude was: by comm, or argv[0] or argv[1] for node. The shell rule and the count apply to all of them, so both sides of the count are agents of any kind. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
…rdr gives Seven cases the plan had no test for: a listing that ends halfway through a process, a stat line that does not parse, a held agent herdr does not answer for, a saved line in a pane herdr says holds no agent, a launch lock that cannot be asked, a Claude started under a Claude, and three stale workspaces whose verdicts must not leak into each other. Each was checked to fail with its branch broken. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
dl --refresh-stale judged every stale workspace once, printed the plan, and then recreated them in turn. A recreate can take minutes, so a workspace that was idle at plan time could have a build or a working agent by the time its turn came, and the recreate ended it. Each workspace the plan would refresh is now judged again just before its recreate. A skip then wins: it is counted as skipped and printed as "Skipping <ws> after all, judged again before its recreate: <why>". The plan is still printed whole, first. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
render_refresh_stale had no test. Four now run the binary against the read-side world with a docker fake that answers the process read: - nothing stale: the "nothing" line, exit 0, no docker exec. - one quiet and one with cc1plus: the whole plan is on stdout before the first devpod up, one recreate, refreshed 1 skipped 1 failed 0. - the first recreate fails: the second still runs, 1 failed, exit 1. - a stopped container: a Skipping line, no up, exit 0. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
…review/refresh-stale
pixi was on the build list, so a pixi shell that stays open in a pane kept that workspace stale for as long as it ran. A build that pixi run starts runs cargo, a compiler or another program the list already names, so the list loses nothing by dropping pixi. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
…a docker fault A stopped stale workspace was skipped as "could not read the processes in its container (docker exec failed ...)". Skipping is right, because a recreate would also start it, but the line said docker was broken. docker exec into a stopped container says "container <id> is not running", so the plan now skips that as Skip::Stopped, and dl says the workspace is stopped and that `dl <ws> recreate` starts it on the new image. (The Skip::Stopped variant and its match arm went in with the previous commit, which touched the same file.) Also: herdr's `done` is an idle agent nobody has looked at, so with the part 2 fix a done agent is held and a refresh may go ahead. The test that pinned the old skip now expects a refresh, and docs/cli.md says why pixi is not on the build list. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
Reviewer's GuideIntroduces Sequence diagram for unattended stale workspace refreshsequenceDiagram
participant User
participant CLI as dl CLI
participant Stale as stale_images
participant Planner as refresh_stale::plan
participant Docker as Docker container
participant Herdr as herdr
participant Launch as Launch
User->>CLI: --refresh-stale
CLI->>Stale: stale_images(workspaces)
Stale-->>CLI: stale images and container ids
CLI->>Planner: plan(stale workspaces)
Planner->>Docker: exec_as_root(read process table)
Docker-->>Planner: processes or unreadable
Planner->>Herdr: collect and read agent sessions
Herdr-->>Planner: pane states and saved lines
Planner-->>CLI: Refresh or Skip verdicts
CLI->>User: Print complete plan
loop Each planned refresh
CLI->>Planner: plan(workspace) again
Planner-->>CLI: Refresh or Skip verdict
alt Still eligible
CLI->>Launch: render_launch(RecreateUnattended)
Launch->>Launch: Recreate container
Launch->>Herdr: Restart held agent panes
Launch-->>CLI: Success or failure
else No longer eligible
CLI->>User: Skipping after all
end
end
CLI->>User: Print tally and exit status
Flow diagram for stale workspace eligibility checksflowchart TD
A[Stale workspace] --> B{Launch lock free?}
B -- No --> S1[Skip: another dl is launching it]
B -- Yes --> C[Read processes with docker exec as root]
C --> D{Container stopped?}
D -- Yes --> S2[Skip: it is stopped]
D -- No --> E{Processes readable?}
E -- No --> S3[Skip: processes unreadable]
E -- Yes --> F{Build process present?}
F -- Yes --> S4[Skip: build is running]
F -- No --> G{Agent has shell child?}
G -- Yes --> S5[Skip: agent is running a command]
G -- No --> H{Agent sessions present?}
H -- No --> R[Refresh]
H -- Yes --> I{Sessions visible and recoverable?}
I -- No --> S6[Skip: sessions unavailable or unresumable]
I -- Yes --> J{Every agent idle or done?}
J -- No --> S7[Skip: agent is busy or unanswered]
J -- Yes --> K{Running agents <= herdr panes?}
K -- No --> S8[Skip: sessions outside panes]
K -- Yes --> R
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codecov Report❌ Patch coverage is Additional details and impacted files
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Copied from the public-api CI job's diff. public-api.api.txt gains LaunchVerb::RecreateUnattended, a change to the promised API: a caller that matches LaunchVerb exhaustively needs an arm for it. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #673 — A workspace keeps its old image after a pull, and a recreate ends its agent. This PR is part 3 of 3:
dl --refresh-stalerecreates each stale workspace whose agents are idle. It can run after the daily pull.What this changes
dl --refresh-stale. It takes no workspace, no-yand no--force. It is in the README command table,dl.bashglobal_opts, the--helpexamples anddocs/cli.md.flows/refresh_stale.rs, makes the plan. It changes nothing. For each stale workspace from part 1, it gives "refresh", or "skip" with the first rule that fails.LaunchVerb::RecreateUnattended, runs the recreate path of part 2 with no attach. Part 2 then starts the agents again in their herdr panes.StaleImage::container()gives the container id that part 1 already reads.rust/dl/src/render.rs.Where this fits
"Agent" means each agent that
dlknows by name:claude,codexandgemini, run directly or bynode. herdr'sdonemeans "idle, and nobody has looked at it yet", soidleanddoneboth let a refresh go ahead.How I checked it
All tests run on
FakeRunner, or on the real binary with a fakedockeronPATH. This devcontainer has no herdr and no docker daemon.node, a process list that ends early, a stat line that does not parse, herdr that does not answer, and several workspaces with different verdicts. I broke three rules on purpose, and five tests failed.rust/dl/tests/read_side.rs): nothing stale, the plan printed before any recreate, exit 1 with the loop going on after a failed recreate, and a stopped workspace skipped as "it is stopped".cargo test --workspace, clippy with-D warnings,cargo fmt --check: clean.pytest test/against the debug build: 850 passed.prek run --from-ref origin/main: passed.I did not run:
scripts/public-api-snapshots.sh. The tools are not in the container. I copied the diff from thepublic-apiCI job into the snapshot files.Merge danger
Door: two-way. The flag is new, and nothing else calls the new verb. The promised API (
public-api.api.txt) gainsLaunchVerb::RecreateUnattended, so a caller with an exhaustivematchonLaunchVerbneeds an arm for it.Blast radius: medium. A false "refresh" ends a working agent. Each rule therefore skips when it is in doubt. These limits remain:
dl <ws> recreate.docs/cli.mdsays this.cmdlinewords, to end in an agent name. A native install that starts a versioned binary by its own path is not seen as an agent. If it runs outside herdr, a refresh ends it.Stack
🤖 Generated with Claude Code
Summary by Sourcery
Add unattended stale-workspace refreshes that safely recreate eligible workspaces and restore their idle agents.
New Features:
dl --refresh-stalecommand to recreate stale workspaces whose agents are idle and restart those agents without attaching.Enhancements:
Documentation:
Tests:
Chores: