Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions cmd/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,21 @@ func upLocal(opts upLocalOpts) error {
return fmt.Errorf("rendering payload: %w", err)
}

// Resolve payload entries into upload pairs
// Resolve payload entries into upload pairs. Inline content payloads are
// written to temp files that are uploaded individually by their own
// sandbox_path, so their source paths must survive until SandboxCreate.
// They MUST NOT live inside payloadDir: createSandbox renames payloadDir
// into a staging directory, which would invalidate any path pointing inside
// it and fail every upload with "local path does not exist" (issue #84).
var extraUploads []gateway.Upload
if opts.harness != nil && len(opts.harness.Payloads) > 0 {
resolved, err := agent.ResolvePayloads(opts.harness.Payloads, opts.harnessDir, payloadDir)
contentDir, err := os.MkdirTemp("", "harness-payload-content-")
if err != nil {
return fmt.Errorf("creating payload content dir: %w", err)
}
defer os.RemoveAll(contentDir)

resolved, err := agent.ResolvePayloads(opts.harness.Payloads, opts.harnessDir, contentDir)
if err != nil {
return fmt.Errorf("resolving payloads: %w", err)
}
Expand Down
48 changes: 48 additions & 0 deletions cmd/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"testing"

"github.com/stackrox/harness-openshell/internal/agent"
"github.com/stackrox/harness-openshell/internal/gateway"
)

Expand Down Expand Up @@ -264,3 +265,50 @@ providers:
t.Fatal("expected error for --agent research when file doesn't exist, should not fall back to embedded")
}
}

// TestUpLocal_InlineContentPayloadPathSurvivesRename is a regression test for
// issue #84: inline content payloads were written into payloadDir, which
// createSandbox renames into a staging directory. The rename invalidated the
// stored upload paths, so every SandboxCreate failed with "local path does not
// exist". The fix stages inline content in a separate temp dir that survives
// the rename — so every upload Src must still resolve at create time.
func TestUpLocal_InlineContentPayloadPathSurvivesRename(t *testing.T) {
dir := setupTestAgent(t)

var uploadsChecked bool
gw := &mockGW{
providers: map[string]bool{"github": true, "google-vertex-ai": true, "atlassian": true},
onSandboxCreate: func(opts gateway.SandboxCreateOpts) error {
for _, u := range opts.Uploads {
if _, err := os.Stat(u.Src); err != nil {
return fmt.Errorf("upload Src %q not accessible at create time: %w", u.Src, err)
}
}
uploadsChecked = true
return nil
},
}

harness := &agent.Harness{
Payloads: []agent.PayloadEntry{
{SandboxPath: "/sandbox/opencode.json", Content: `{"model": "test"}`},
},
}

err := upLocal(upLocalOpts{
harnessDir: dir,
gw: gw,
agentPath: filepath.Join(dir, "agents", "default.yaml"),
noTTY: true,
harness: harness,
})
if err != nil {
t.Fatalf("upLocal: %v", err)
}
if !uploadsChecked {
t.Fatal("SandboxCreate was not called; upload paths were never validated")
}
if gw.createCalls != 1 {
t.Fatalf("expected exactly 1 SandboxCreate call, got %d", gw.createCalls)
}
}
6 changes: 6 additions & 0 deletions cmd/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type mockGW struct {
deletedNames []string
gatewayListResult []gateway.GatewayInfo
onGatewayRemove func(string)
onSandboxCreate func(gateway.SandboxCreateOpts) error
}

func (m *mockGW) InferenceGet() error { return m.inferenceErr }
Expand All @@ -41,6 +42,11 @@ func (m *mockGW) ProviderList() ([]string, error) { return m.providerList, m.pro
func (m *mockGW) SandboxCreate(opts gateway.SandboxCreateOpts) error {
m.createCalls++
m.createOpts = append(m.createOpts, opts)
if m.onSandboxCreate != nil {
if err := m.onSandboxCreate(opts); err != nil {
return err
}
}
if m.createErr != nil && m.createCalls == 1 {
return m.createErr
}
Expand Down
Loading