Skip to content

feat(load_artifact): allow urls entries to set the saved filename - #103

Merged
earakely-scale merged 1 commit into
mainfrom
tejas/load-artifact-url-filenames
Oct 8, 2026
Merged

earakely-scale merged 1 commit into
mainfrom
tejas/load-artifact-url-filenames

Conversation

@polakamtejas

@polakamtejas polakamtejas commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

What and why

load_artifact names each URL download after the last segment of the URL path. When that segment is an opaque object key, as it often is for object-storage and signed URLs, the file lands with no real name and no extension. The prompt meanwhile refers to the file by its real name, and the agent has no way to match the two.

A urls entry may now be {"url": ..., "filename": ...} as well as a bare URL string:

{"id": "load-inputs", "type": "load_artifact", "agent_name": "agent", "destination_path": "/tmp/data",
 "urls": [
   {"url": "https://files.example/objects/obj-4f9c2a", "filename": "report.txt"},
   "https://a.example/data.csv"
 ]}

This saves /tmp/data/report.txt and /tmp/data/data.csv, and loaded_urls[].files records both names.

  • A bare string is named exactly as before: the last path segment, percent-decoded, downloaded when empty, and repeats suffixed -1, -2, ….
  • An object entry takes a non-empty url and filename and no other key. The filename must be a plain file name: relative, no .., no path separators.
    • Nested paths are left out on purpose: ./a vs a would get past the duplicate check, and a vs a/b would put a file inside a directory instead of failing.
    • Allowing them later is backward compatible.
  • An explicit filename is never renamed. The same filename twice, or a bare URL whose name would match an explicit one, raises an error instead of being suffixed.
  • Entries are validated at construction, so __init__ and from_dict reject a bad stored entry. A per-run urls override is validated in _resolve_inputs, before anything in the step is loaded.
  • to_dict and from_dict pass entries through unchanged.

Compatibility: existing steps are unchanged. A step that uses the object form needs a version with this change to run. An older version accepts the step but fails at run time when it names the downloads.

How it was tested

  • make unit-test: 6544 passed, 13 skipped.
  • 13 new test cases in tst/unit/task_step/test_load_artifact_{container_sandbox,overrides,url_filenames,vm_target}.py:
    • bare-URL naming: suffix, fallback name and percent-decoding. No test covered it before, and the code moved.
    • a mixed list run end to end: where each file is written, and loaded_urls.
    • a named URL on the VM-host path.
    • a named entry in a per-run override.
    • an invalid override failing before anything is loaded.
    • the round-trip.
    • five invalid entries: missing filename, an unknown key, .., an absolute path, a nested path.
    • a duplicate filename.
    • a bare URL colliding with an explicit filename.
  • Disabling each rule in turn (explicit name ignored, duplicate check, collision check, plain-name check, construction-time validation, override validation) makes at least one of these tests fail.
  • .github/scripts/check_plugin_api.py: no break to the plugin surface.
  • Downloads go through mocked sandboxes only. No integration test covers urls.

🤖 Generated with Claude Code

RetriggerConfidence Score: 5/5

The PR appears safe to merge.

What we checked:

  • Chosen filenames overwrite each other: The step rejects duplicate chosen names and rejects a bare URL when its generated name matches a chosen name, before downloads start.

Summary

load_artifact URL lists can now mix bare URLs with entries that choose a filename. Chosen names are used for downloads and recorded in loaded_urls[].files.

  • URL downloads can now keep the filenames task authors give them.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["urls entry"] --> B{"Entry type"}
  B -->|Bare URL| C["Name from URL path"]
  B -->|Object| D["Check chosen filename"]
  C --> E["Check against chosen names"]
  D --> E
  E --> F["Download to target"]
  F --> G["Record loaded_urls"]
Loading

Reviews (1) · Last reviewed commit: "feat(load_artifact): allow urls entries ..." · Reviewed by Greptile

A `urls` entry may now be {"url": ..., "filename": ...} as well as a bare
URL string. A bare string keeps today's naming exactly (last path segment,
percent-decoded, "downloaded" when empty, repeats suffixed -1, -2, ...);
an object entry is saved as <destination_path>/<filename> verbatim, so a
URL with no usable name (an opaque object key) can land under the name the
prompt refers to.

Entries are validated and named by one function, called at construction for
the stored urls and in _resolve_inputs for a per-run `urls` override, so a
bad override fails before anything is loaded. An object entry needs a
non-empty string url and filename and no other key; the filename must pass
validate_relative_filename and be a plain file name (no path separators,
not "."). An explicit filename is never renamed: one given twice, or one a
bare URL's derived name would match, raises instead of being suffixed.

loaded_urls[].files records the names as written. to_dict/from_dict round-
trip the entries unchanged: strings stay strings, objects stay objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@polakamtejas
polakamtejas requested a review from a team as a code owner October 7, 2026 20:50

@earakely-scale earakely-scale left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does what it says. It merges cleanly with current main, and the unit tier passes on the merge.

One suggestion, inline: names derived from bare URLs skip the rules explicit names now get.

Heads-up for consumers: anything that pre-validates urls as strings, such as a URL scheme check, needs to read entry["url"] from object entries too, or those entries skip it.

Comment thread src/agent_env/task_step/task_steps/load_artifact.py
@earakely-scale
earakely-scale merged commit adf9193 into main Oct 8, 2026
13 checks passed
@earakely-scale
earakely-scale deleted the tejas/load-artifact-url-filenames branch October 8, 2026 03:20
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.

2 participants