Skip to content

bug: openshell sandbox upload <local-dir> <remote-dir> flattens contents instead of creating named subdirectory #885

Description

@mjamiv

Agent Diagnostic

  • Loaded openshell-cli skill and walked the sandbox upload command path in crates/openshell-cli/.
  • When <local> is a directory, the CLI walks the directory and uploads each entry into <remote> directly. The local directory's name is dropped from the destination path.
  • Confirmed by the minimal repro below. Behavior diverges from the standard scp -r and cp -r semantics operators expect from any *nix-style file-transfer tool.
  • Agent identified the workaround: tar -czf foo.tgz -C parent foo && openshell sandbox upload <name> foo.tgz /tmp/ && ssh ... 'tar -xzf /tmp/foo.tgz -C <remote>/'. The tarball preserves the source-directory prefix deterministically.

Description

openshell sandbox upload <local-dir> <remote-dir> flattens the local directory's contents into the remote destination instead of creating a subdirectory named after the local directory.

In our case this caused a real near-miss: a directory of content was uploaded into a sibling location alongside existing files at the destination. No data was lost (all uploaded files were new and didn't collide), but the recovery path required a full diff against backups to confirm. A future variant of the same operation could silently shadow files at the destination with no warning.

Reproduction Steps

  1. Create a local directory with content:
    mkdir -p /tmp/upload-test/sub
    echo a > /tmp/upload-test/a.txt
    echo b > /tmp/upload-test/sub/b.txt
    
  2. Upload it: openshell sandbox upload <name> /tmp/upload-test /sandbox/dest/
  3. SSH in and inspect: ssh <sandbox> 'ls /sandbox/dest/'
  4. Observed: a.txt sub/ — the upload-test/ prefix is gone; contents are flat in /sandbox/dest/.
  5. Expected: upload-test/ containing a.txt and sub/b.txt, mirroring scp -r ./upload-test <remote>:/sandbox/dest/.

Environment

  • OpenShell: v0.0.31 host CLI, v0.0.31 supervisor
  • OS: Ubuntu 24.04 LTS
  • Docker: 27.x
  • Cluster image: ghcr.io/nvidia/openshell/cluster:0.0.16

Proposed direction

Two options:

  1. Match scp -r / cp -r semantics — preserve the source directory's basename as a subdirectory of the destination. Principle-of-least-surprise fix; aligns with every comparable file-transfer tool.
  2. Keep current flatten behavior, add a --preserve-dir flag for the standard semantics, plus a CLI help update calling out the divergence loudly.

I prefer (1). The current behavior is a quiet footgun for any operator with prior file-transfer experience. Cost is one breaking-change note in the release. Happy to convert this to a PR if a maintainer signals direction.

Agent-First Checklist

  • I pointed my agent at the repo and had it investigate this issue
  • I loaded relevant skills (openshell-cli)
  • My agent could not resolve this — the diagnostic above explains the root cause; resolution needs an upstream code change.

Activity

  1. github-actions commented on Apr 19, 2026

    @github-actions

    This issue appears to have been opened without an agent investigation.

    OpenShell is an agent-first project - please point your coding agent at the repo and have it diagnose this before we triage. Your agent can load skills like debug-openshell-cluster, debug-inference, openshell-cli, and generate-sandbox-policy.

    See CONTRIBUTING.md for the full workflow.

  2. added 2 commits that reference this issue on Apr 19, 2026
    106d4e1
    c49a3bf
  3. mjamiv commented on Apr 19, 2026

    @mjamiv
    ContributorAuthor

    Both proposed-direction variants are pushed to the fork as reference branches, ready to convert to PRs once a maintainer signals direction. Each compiles clean and includes unit tests for the directory-prefix logic.

    Variant A — match scp -r semantics by default (breaking)
    Branch: fix/sandbox-upload-preserve-dir-default
    Diff: main...mjamiv:OpenShell:fix/sandbox-upload-preserve-dir-default

    Single-file change to crates/openshell-cli/src/ssh.rs. Always wraps a directory's contents under the source basename. Paths without a meaningful basename (., /) preserve the legacy flat extraction. Tests cover both branches.

    Variant B — opt-in --preserve-dir flag (non-breaking)
    Branch: feat/sandbox-upload-preserve-dir-flag
    Diff: main...mjamiv:OpenShell:feat/sandbox-upload-preserve-dir-flag

    Adds preserve_dir_name: bool parameter to sandbox_sync_up (with false for backward compat at non-CLI call sites), wires --preserve-dir into the Upload command, and updates the command help to call out the default-flatten behavior so operators discover the flag without a production incident first.

    Variant A is what I'd reach for personally — the breaking change is small (one release-note line) and the current behavior is a quiet footgun. Variant B is the safer option if backward compatibility weighs heavier. Happy to open whichever you prefer as a PR; reply with the variant name and I'll mark it ready for review.

  4. mjamiv commented on Apr 19, 2026

    @mjamiv
    ContributorAuthor

    Heads-up for triage: the body has been edited from H2 (## ) to H3 (### ) headers so the agent diagnostic section now matches the regex at .github/workflows/issue-triage.yml:26. The bot's only complaint was header level — the diagnostic content was substantive on first filing. Safe to drop the state:triage-needed label on review.

  5. johntmyers commented on Apr 20, 2026

    @johntmyers
    Collaborator

    Thanks @mjamiv - I agree on variant A. Happy to reivew a PR.

  6. assigned and unassigned on Apr 21, 2026
  7. added a commit that references this issue on Apr 24, 2026
    e24bd71
  8. added a commit that references this issue on Apr 24, 2026
    0d301d5
  9. removed
    state:triage-neededOpened without agent diagnostics and needs triage
    on Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions