Skip to content

Capture agent-created files as Octopus artifacts via a manifest#2042

Merged
zentron merged 16 commits into
mainfrom
aiagent-artifact-manifest
Jun 25, 2026
Merged

Capture agent-created files as Octopus artifacts via a manifest#2042
zentron merged 16 commits into
mainfrom
aiagent-artifact-manifest

Conversation

@zentron

@zentron zentron commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Background

The AiAgent (Claude Code) step runs in a temporary working directory that is
deleted when the step ends. Users frequently ask the agent to produce files —
a dated report, a CSV, a generated site — and want them surfaced as Octopus
artifacts, but there was no mechanism to do so.

What

  • ArtifactManifestCollector — reads a manifest the agent writes
    (<workingDir>/.octopus/artifacts.jsonl, one {"path","name"} per line),
    validates each entry resolves (canonically, symlinks + .. resolved) strictly
    inside the working dir — rejecting the working-dir root so step scaffolding
    can't be attached — then copies files (or zips directories) out to the durable
    Calamari directory and emits NewOctopusArtifact. Takes IVariables so it
    resolves its own configuration.
  • octopus-artifacts skill (always on) — instructs the agent to declare
    artifacts in the manifest only when the user explicitly asks, using Write
    (no Bash needed).
  • Total upload size limit — the collector accumulates each captured
    artifact's size and hard-fails (CommandException) once the running total
    exceeds a configurable maximum. Defaults to 5 GiB; override with the new
    Octopus.Action.Claude.MaxArtifactSizeInMegaBytes variable (megabytes, read
    via GetInt32 — same convention as OctopusFreeDiskSpaceOverrideInMegaBytes).
    Messages use ToFileSizeString() for human-readable sizes.
  • Wired into InvokeClaudeCodeBehaviour after the run, before the temp dir is
    disposed. An invalid manifest entry hard-fails the step (per ADR-004).

Resolves

Testing

  • Collector unit tests: single/multiple files, directory→zip, name defaulting,
    basename-collision via relative path, the hard-fail cases (missing, malformed
    JSON, empty path, absolute-path/symlink escape, working-dir root, empty dir),
    and the total-size limit (within-limit passes; exceeding throws; default is
    5 GiB). Plus a SkillsWriter test for the new skill. The fixture initialises
    per test via TemporaryDirectory and drives the size limit through an injected
    IVariables.
  • Full non-integration suite green.
  • Added an [Category("Integration")] smoke test (needs ANTHROPIC_TOKEN; not
    run in CI).

How to review

  • Core logic is ArtifactManifestCollector.cs; the security boundary
    (Canonical + containment check) and the cumulative size check are the parts
    worth scrutiny.
  • Everything else is wiring (collector reads the size variable itself) and the
    skill markdown.
  • Decision + rationale recorded in ADR-011 (OctopusDeploy/adr#248).

No server change required — uses the existing createArtifact service message.

🤖 Generated with Claude Code

zentron and others added 11 commits June 24, 2026 21:46
Captures files/directories created by the Claude Code AiAgent step as
Octopus artifacts via an explicit manifest the agent writes, with
Calamari-managed validation and capture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@zentron zentron force-pushed the aiagent-artifact-manifest branch from 0f40599 to 1379a0f Compare June 25, 2026 04:19

@liam-mackie liam-mackie left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor nits, but otherwise LGTM. I glossed over the tests since they seemed claude-generated, so if you're concerned with them, let me know and I'll spend more time on them (note that I saw one that asserted that a constant was a certain size - feels useless imo, so may be worth cleaning up useless tests)

@@ -0,0 +1,19 @@
---
name: octopus-artifacts
description: Use ONLY when the user explicitly asks to attach, upload, or save output or files as an Octopus artifact. Do not infer artifacts from a request that merely creates files. It is important that this skill be used however if the customer expects the file or directory to be attached to the deployment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: you may find that this skill's internal contradiction ("use ONLY when..." vs. "This skill should be used if the customer expects...") I re-wrote it slightly into something more explicit, including keywords to trigger more consistent activation:

Suggested change
description: Use ONLY when the user explicitly asks to attach, upload, or save output or files as an Octopus artifact. Do not infer artifacts from a request that merely creates files. It is important that this skill be used however if the customer expects the file or directory to be attached to the deployment.
description: Use when the user wants a file, directory, or command output attached, uploaded, published, or saved to an Octopus deployment or released as an artifact, or anything they expect to appear in the Octopus portal or be downloadable from the deployment afterward. Keywords: "Octopus artifact", "attach/upload to the deployment", "publish to the release", "make this available/downloadable in Octopus". Do NOT use when the request is only to create, generate, or write files locally. Producing a file is not attaching an artifact unless the user expects it to surface in Octopus.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Adjusted a little to avoid false positives, but 👍

Comment on lines +130 to +132
// Resolves a symlinked leaf to its real target so a link inside the working dir
// cannot point at a file outside it. (Symlinked intermediate directories are a
// known v1 limitation — see the design doc follow-ups.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: obvious claude code comment that adds minimal real value to the comprehension of the function

@zentron zentron enabled auto-merge (squash) June 25, 2026 10:53
@zentron zentron merged commit d3a6f54 into main Jun 25, 2026
34 checks passed
@zentron zentron deleted the aiagent-artifact-manifest branch June 25, 2026 11:28
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