Skip to content

fix(delivery): cleanup build files for mounted volume#615

Merged
skevetter merged 9 commits into
mainfrom
fix/seed-workspace-tree-pollution
Jul 7, 2026
Merged

fix(delivery): cleanup build files for mounted volume#615
skevetter merged 9 commits into
mainfrom
fix/seed-workspace-tree-pollution

Conversation

@skevetter

@skevetter skevetter commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up fix for the workspace-volume seeding added in #605. On a seeded workspace, git status showed devsy artifacts as uncommitted/untracked content:

new file:   .devcontainer/skevetter/Dockerfile
...
Untracked files:
    .devcontainer/skevetter/.devsy-internal/
    .devsy-seeded

The workspace volume is mounted at the workspace folder, so the volume root is the git working tree. Two seed artifacts leaked into it:

  • .devsy-seeded — the seed marker was written as a file at the volume root. It is now tracked as an sh.devsy.seeded volume label instead (set at volume creation). The volume is removed on copy failure so the label never persists for an empty volume, preserving idempotent re-seed behavior.
  • .devsy-internal/ — devsy's transient Dockerfile-build folder was captured by the cp -a seed (the build runs before the seed and its cleanup is deferred). The copy now uses tar with --exclude for that folder, preserving attributes/permissions.

Verification

Verified on an SSH provider with a Dockerfile-based devcontainer:

  • Seeded workspace tree contains only project content — no .devsy-seeded, no .devsy-internal.
  • .git, file contents, and executable bits preserved.
  • Label-based idempotency still skips re-seeding on subsequent ups (in-volume edits persist).

Summary by CodeRabbit

  • New Features
    • Workspace seeding and content streaming now skip build artifacts, helping keep synced and uploaded content cleaner.
  • Bug Fixes
    • Seeded workspace volumes are now tracked more reliably, and failed seeding attempts clean up the volume for a fresh retry.
    • Build cleanup now removes build-related artifacts more thoroughly, reducing leftover files between runs.

The workspace volume is mounted at the workspace folder, so its root is the
git working tree. Two seed artifacts leaked into it:

- The .devsy-seeded sentinel was written at the volume root, showing up as an
  untracked file. Track seeded state with an sh.devsy.seeded volume label
  instead (set at creation; the volume is removed on copy failure so the label
  never persists for an empty volume).
- The cp -a copy pulled in devsy's transient .devsy-internal build folder.
  Switch to a tar copy that excludes it (preserving attributes/perms).

Verified on ws3-ssh: the seeded workspace tree now contains only project
content (no .devsy-seeded, no .devsy-internal), perms/.git preserved, and the
label-based idempotency still skips re-seeding on subsequent ups.
@netlify

netlify Bot commented Jul 7, 2026

Copy link
Copy Markdown

Deploy Preview for devsydev canceled.

Name Link
🔨 Latest commit fe87390
🔍 Latest deploy log https://app.netlify.com/projects/devsydev/deploys/6a4d5abb5770f40008484da9

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de2b9184-5cde-4959-a89d-3c9d62be9626

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@netlify

netlify Bot commented Jul 7, 2026

Copy link
Copy Markdown

Deploy Preview for images-devsy-sh ready!

Name Link
🔨 Latest commit fe87390
🔍 Latest deploy log https://app.netlify.com/projects/images-devsy-sh/deploys/6a4d5abbb02a7a0008aff601
😎 Deploy Preview https://deploy-preview-615--images-devsy-sh.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the size/m label Jul 7, 2026
skevetter added 2 commits July 7, 2026 12:05
…up eagerly

Make the handling of devsy's build scaffolding (.devsy-internal) explicit and
robust instead of implicit and scattered:

- Add a single source of truth in pkg/devcontainer/config/artifacts.go:
  buildArtifactNames plus BuildArtifactExcludes() and RemoveBuildArtifacts().
  Adding a future artifact now only requires updating that list.
- Route every consumer through it: build/prebuild cleanup, the prebuild-hash
  exclude, and the workspace-volume seed tar-exclude no longer hard-code the
  folder name.
- Fix the root cause of the seed-pollution bug: build() now removes the
  artifacts eagerly on the success path, so they are gone before any later
  step that copies the workspace tree. The deferred cleanup in Up/Build
  remains as an error-path safety net, and the seed exclude as defense in depth.

Verified on ws3-ssh: a Dockerfile-based workspace seeds a clean tree (no
.devsy-internal, no .devsy-seeded), .git and perms preserved, seeded label set.
Follow-up to the artifact centralization: reduce the verbose doc/inline
comments to short why-notes now that the helpers and flow are self-documenting.
No behavior change.
@github-actions github-actions Bot added size/l and removed size/m labels Jul 7, 2026
@skevetter skevetter changed the title fix(delivery): stop workspace volume seeding from polluting the git tree fix(delivery): cleanup build files for mounted volume Jul 7, 2026
Dockerless (kubernetes) builds defer the actual image build to inside the
container: build() prepares .devsy-internal on the host, stashes it, and kaniko
consumes it later. The eager cleanup added for volume-seed hygiene deleted
those artifacts prematurely, breaking dockerless builds with
'rename dir: .devsy-internal: no such file or directory'.

Only clean up eagerly when the build produced an image (buildInfo.Dockerless
== nil); the dockerless flow manages its own artifact lifecycle. Volume seeding
never runs on the dockerless/kubernetes path, so tree pollution is not a
concern there.

Verified on an orbstack kubernetes cluster: dockerless up now succeeds and the
pod runs.
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

⚠️ This PR contains unsigned commits. To get your PR merged, please sign those commits (git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}) and force push them to this branch (git push --force-with-lease).

If you're new to commit signing, there are different ways to set it up:

Sign commits with gpg

Follow the steps below to set up commit signing with gpg:

  1. Generate a GPG key
  2. Add the GPG key to your GitHub account
  3. Configure git to use your GPG key for commit signing
Sign commits with ssh-agent

Follow the steps below to set up commit signing with ssh-agent:

  1. Generate an SSH key and add it to ssh-agent
  2. Add the SSH key to your GitHub account
  3. Configure git to use your SSH key for commit signing
Sign commits with 1Password

You can also sign commits using 1Password, which lets you sign commits with biometrics without the signing key leaving the local 1Password process.

Learn how to use 1Password to sign your commits.

Watch the demo

Replace the timing-dependent eager RemoveBuildArtifacts in build() (which
needed a dockerless carve-out to avoid deleting .devsy-internal before
kaniko's second build phase) with the exclude-at-copy-time guarantee.

The correctness invariant is now that any operation copying, streaming, or
hashing a workspace tree excludes build artifacts via BuildArtifactExcludes;
RemoveBuildArtifacts is best-effort tidiness only. Add the exclude to the
StreamWorkspace and StreamMount tunnel paths, which previously honored only
.devsyignore.
@skevetter
skevetter force-pushed the fix/seed-workspace-tree-pollution branch from 975e1de to a7b1134 Compare July 7, 2026 18:23
@skevetter
skevetter marked this pull request as ready for review July 7, 2026 19:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
pkg/devcontainer/config/artifacts.go (1)

19-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cleanup errors are silently discarded.

RemoveBuildArtifacts swallows all os.RemoveAll errors with _ =. Since this helper is now shared across build/prebuild cleanup and workspace seeding cleanup-on-failure paths, silent failures (e.g., permission denied, busy file) could leave stale artifacts without any diagnostic trail.

♻️ Suggested improvement to surface failures via logging
-func RemoveBuildArtifacts(contextPath string) {
+func RemoveBuildArtifacts(contextPath string) {
 	for _, name := range buildArtifactNames {
-		_ = os.RemoveAll(filepath.Join(contextPath, name))
+		if err := os.RemoveAll(filepath.Join(contextPath, name)); err != nil {
+			log.Debugf("failed to remove build artifact %s: %v", name, err)
+		}
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/devcontainer/config/artifacts.go` around lines 19 - 23,
RemoveBuildArtifacts is discarding cleanup failures from os.RemoveAll, so update
this helper to surface errors instead of ignoring them. Use the existing
RemoveBuildArtifacts symbol in artifacts.go to either return an error or log
each failure with enough context (artifact name and path) so callers in
build/prebuild cleanup and workspace seeding can detect and diagnose
stale-artifact problems.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/agent/delivery/workspace_seed.go`:
- Around line 56-64: The workspace seeding flow in
createVolume/copyDirIntoVolume marks the volume as seeded too early by setting
pkgconfig.DockerSeededLabel before the copy finishes. Move the label assignment
so it only happens after copyDirIntoVolume succeeds, and in the failure path for
removeVolume either propagate the cleanup error or otherwise prevent the partial
volume from being treated as complete on the next run.
- Around line 121-129: The volumeSeedState logic now depends only on Docker
labels, so legacy managed volumes that predate DockerSeededLabel are being
misclassified as unseeded and reseeded. Update volumeSeedState to handle
existing managed volumes conservatively by detecting the missing seeded label
and preserving seeded state unless an explicit reset path is taken, or add a
migration/fallback for legacy volumes. Use the Docker inspect parsing in
volumeSeedState and the managed/seeded label checks to keep backward
compatibility.

---

Nitpick comments:
In `@pkg/devcontainer/config/artifacts.go`:
- Around line 19-23: RemoveBuildArtifacts is discarding cleanup failures from
os.RemoveAll, so update this helper to surface errors instead of ignoring them.
Use the existing RemoveBuildArtifacts symbol in artifacts.go to either return an
error or log each failure with enough context (artifact name and path) so
callers in build/prebuild cleanup and workspace seeding can detect and diagnose
stale-artifact problems.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e43e988-8651-4315-a611-59d9f718b03c

📥 Commits

Reviewing files that changed from the base of the PR and between bed699a and f709c89.

📒 Files selected for processing (8)
  • pkg/agent/delivery/local_docker_test.go
  • pkg/agent/delivery/workspace_seed.go
  • pkg/agent/tunnelserver/tunnelserver.go
  • pkg/devcontainer/build.go
  • pkg/devcontainer/config/artifacts.go
  • pkg/devcontainer/config/artifacts_test.go
  • pkg/devcontainer/config/prebuild.go
  • pkg/devcontainer/prebuild.go

Comment thread pkg/agent/delivery/workspace_seed.go
Comment on lines +121 to +129
"--format", "{{index .Labels \""+pkgconfig.DockerManagedLabel+"\"}},"+
"{{index .Labels \""+pkgconfig.DockerSeededLabel+"\"}}",
name,
).CombinedOutput()
if err != nil {
return false, false, fmt.Errorf("inspect volume %s: %s: %w", name, string(out), err)
}
managed = strings.TrimSpace(string(out)) == "true"

return managed, d.volumeSeeded(ctx, name), nil
}

// volumeSeeded reports whether the seeded sentinel file exists in the volume.
func (d *LocalDockerDelivery) volumeSeeded(ctx context.Context, name string) bool {
args := []string{
cmdRun, flagRM,
"-v", name + ":/target:ro",
d.helperImageName(),
"sh", "-c", "[ -e /target/" + seededSentinel + " ]",
}
return d.cmd(ctx, args...).Run() == nil
managedStr, seededStr, _ := strings.Cut(strings.TrimSpace(string(out)), ",")
return managedStr == pkgconfig.LabelValueTrue, seededStr == pkgconfig.LabelValueTrue, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether any legacy sentinel fallback remains around the new label-only path.
rg -n '\.devsy-seeded|seededSentinel|DockerSeededLabel|volumeSeedState|prepareSeedTarget' .

Repository: devsy-org/devsy

Length of output: 1012


🏁 Script executed:

#!/bin/bash
sed -n '1,220p' pkg/agent/delivery/workspace_seed.go

Repository: devsy-org/devsy

Length of output: 5308


🏁 Script executed:

#!/bin/bash
rg -n "func .*createVolume|createVolume\\(|DockerManagedLabel|DockerSeededLabel|devsy-seeded|seeded" pkg/agent/delivery pkg/config -g '!**/*_test.go'

Repository: devsy-org/devsy

Length of output: 2376


🏁 Script executed:

#!/bin/bash
sed -n '1,220p' pkg/agent/delivery/local_docker.go

Repository: devsy-org/devsy

Length of output: 5915


🏁 Script executed:

#!/bin/bash
rg -n "workspace volume|seed workspace|already seeded|--reset|sentinel|devsy-seeded|seeded volume" README.md docs pkg -g '!**/*_test.go'

Repository: devsy-org/devsy

Length of output: 2478


🏁 Script executed:

#!/bin/bash
sed -n '450,540p' pkg/devcontainer/single.go

Repository: devsy-org/devsy

Length of output: 3096


Preserve seeded-state compatibility for existing managed volumes. volumeSeedState now relies only on Docker labels, so a managed volume created before DockerSeededLabel existed is treated as unseeded and gets reseeded in place. Add a migration/fallback for legacy seeded volumes, or treat managed volumes missing the new label conservatively unless --reset is set.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/agent/delivery/workspace_seed.go` around lines 121 - 129, The
volumeSeedState logic now depends only on Docker labels, so legacy managed
volumes that predate DockerSeededLabel are being misclassified as unseeded and
reseeded. Update volumeSeedState to handle existing managed volumes
conservatively by detecting the missing seeded label and preserving seeded state
unless an explicit reset path is taken, or add a migration/fallback for legacy
volumes. Use the Docker inspect parsing in volumeSeedState and the
managed/seeded label checks to keep backward compatibility.

skevetter added 3 commits July 7, 2026 14:51
…failures

Volumes seeded before the seeded label existed are marked only by the
legacy .devsy-seeded sentinel file; recognize it in volumeSeedState so
GA-created (v1.5.0) workspace volumes are not re-seeded over user changes.

When a seed copy fails and the follow-up volume removal also fails, join
both errors instead of swallowing the cleanup failure, so a partial
volume is never silently treated as complete.

Log RemoveBuildArtifacts failures at debug for a diagnostic trail.
Backward compatibility with pre-label seeded volumes is not required;
volumeSeedState relies solely on the seeded label.
@skevetter
skevetter merged commit 8e32743 into main Jul 7, 2026
60 checks passed
@skevetter
skevetter deleted the fix/seed-workspace-tree-pollution branch July 7, 2026 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant