Repository navigation
feat(bundle): an artifacts/<name>/ folder can be a CLI, installed into agents like a generated one - #198
Merged
Conversation
…o agents like a generated one A folder whose artifact.toml says type = "cli", with command_name and entrypoint, is written as a CliArtifact over the files its walk lists, and checked before any write: the entrypoint is one of its files, command_name is a plain name, and no .env sits beside the entrypoint, where installing writes the gateway URL. CliArtifact.put takes a file listing as SkillArtifact.put does and uploads under a prefix of its own per attempt, so a retry after a failed upload works. The test echo agent runs a prompt's "run <command>" lines. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment on lines
+87
to
+93
| process = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE, | ||
| stderr=asyncio.subprocess.STDOUT) | ||
| try: | ||
| output, _ = await asyncio.wait_for(process.communicate(), RUN_TIMEOUT_SECONDS) | ||
| except asyncio.TimeoutError: | ||
| process.kill() | ||
| return f"ran {command}: timed out after {RUN_TIMEOUT_SECONDS}s" |
There was a problem hiding this comment.
Stopped requests leave commands running
_ran does not stop its command when the request is canceled. Its timeout branch also kills only the shell, so commands with child processes can keep running after the timeout reply. These processes can keep writing files or affect later requests in the same test container.
Start the command in its own process group, stop that group on timeout or cancellation, and wait for it to exit.
Prompt To Fix With AI
This is a comment left during a code review.
Path: tst/data/a2a_agent/agent.py
Line: 87-93
Comment:
**Stopped requests leave commands running**
`_ran` does not stop its command when the request is canceled. Its timeout branch also kills only the shell, so commands with child processes can keep running after the timeout reply. These processes can keep writing files or affect later requests in the same test container.
Start the command in its own process group, stop that group on timeout or cancellation, and wait for it to exit.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Collaborator
Author
There was a problem hiding this comment.
Valid, thanks. Fixed in b0c3cbf: the command starts in a process group of its own, which is killed on timeout or cancellation and then awaited. Checked by hand: a timed-out sleep 30 & sleep 30 leaves no process behind.
… a test agent's run with all it started Files below the .env install writes beside the entrypoint are refused too, since install writes that path as a file. The echo agent's run lines start their command in a process group of their own and stop the group, then wait for it, when the command times out or the request is cancelled. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…fore any write A load_artifact step naming one of the bundle's CLIs must name an agent and an env, whose gateway the CLI is pointed at, and no two of a task's loads may install one command_name into one agent, where the second would replace the first. Both used to fail mid-run, after the deploys. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…le once, reuse the plan's built steps CliArtifact.put checks the entrypoint is among the files only for a listing, so a cli_dir put takes an entrypoint like ./bin/x as before. install_cli and the bundle's check share GATEWAY_ENV_FILE. The plan keeps the steps _build_tasks builds, so the CLI load check reads them rather than building every step again, and checks a step's agent and env once however many CLIs it loads. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… slow test The task also builds the items env's CLI with build_mcp_cli, as the worker does, through CliArtifact.put's cli_dir path, installs it into the agent beside the folder's CLI, and has the agent add an item through it and list it back. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
Summary
A bundle's
artifacts/<name>/folder can now be a CLI: anartifact.tomlwithtype = "cli",command_nameandentrypoint, over the folder's files. Until now such a folder was refused before any write (writing a cli artifact isn't supported yet).A task installs it into an agent the way it installs a generated CLI:
load_artifactwithartifact_id = "hello"(with an env, whose gateway URL install writes beside the entrypoint), thenadd_skillswithcli_artifact_ids = ["hello"].How
CliArtifact.put:cli_dirorfiles(the folder walk's listing), asSkillArtifact.putdoes. Only a listing must hold the entrypoint as one of its keys; acli_dirput checks the file as before, so an entrypoint like./bin/xstill works.ArtifactStore.attempt_prefix), so a retry after a failed upload works. Before, a failed upload left write-once objects at the version's prefix and blocked the retry.build_mcp_cliwrites CLIs throughput, and new CLIs now land atartifacts/cli/<key>/<version>-<attempt>/. Readers go through the file universe's object URLs, so nothing reads the old layout.CliArtifact.from_toml/accept_toml: these check before any write, and every problem is reported together:command_nameandentrypointare required, and nothing else is taken.env_id/env_versionrecord the env a generated CLI came from, so they aren't keys.command_nameis a plain name, since the CLI is installed at/opt/cli/<command_name>..envnext to the entrypoint is refused, because installing writes the gateway URL there.install_cliand this check share the name,GATEWAY_ENV_FILE.load_artifactstep that names one of the bundle's CLIs must name an agent and an env. No two of a task's loads may install onecommand_nameinto one agent, where the second would replace the first. Both used to fail mid-run, after the deploys. The check reads the steps the plan already built (_Planner.steps).plan._FOLDER_WALKS: CLIs useentry_files, as skills do, so the ledger tracks them and rewrites one when its files or toml change.AuthoringContext.try_files(): this is the folder's files, orNonewhen the walk itself fails. Without it,accept_tomlwould report the walk's problem a second time.run <command>prompt line runs that command in its container and adds aran <command>: <output>line.Tests
cli_folder_test.py: a folder written over its files (not its toml) under an attempt prefix; reuse, and rewrites on a file or toml edit; a walk problem reported once; seven refusedartifact.tomlshapes, a load missing its agent or its env, and two CLIs of one command in one agent (refused) or in two (allowed).cli_artifact_test.py: a folder and a listing write the same files; one of the two is required; the entrypoint must be listed; an interrupted put can be retried.run_bundle_cli_folder_test.py(72 s on a Mac):add_skills.lib/greeting.txtand the gateway URL's scheme from the.envinstall wrote. The check also sees thehello-cliskill.lib/greeting.txtwrites v2, and the next run installs and runs the new one.build_mcp_cli, as the worker does, throughput'scli_dirpath. It installs that CLI too, and the agent adds an item through it and lists it back from the gateway.run_bundle_*slow tests that use the echo agent pass with its newrunline.--sandbox modal_vmon local stores: the gateway env and the echo agent on Modal VMs, both CLIs installed into the remote agent. It passed in 203 s, and both sandboxes were torn down. The agent's reply:Limits
sh, needs to make it executable itself.Hot path, through the worker's
build_mcp_cli, so this takes thebump-versionlabel.🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge, with a non-blocking cleanup issue remaining in the test agent.Fix with agent prompt
Summary
Adds support for CLI folders in bundles and uploads each CLI under a fresh attempt prefix.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR A["CLI folder"] --> B["Check settings and files"] B --> C["Upload under fresh attempt prefix"] C --> D["Store CLI and file records"] D --> E["load_artifact"] E --> F["Install files and gateway .env"] F --> G["Agent runs CLI"]Reviews (5) · Last reviewed commit: "test(bundle): cover build_mcp_cli's put ..." · Reviewed by Greptile