Skip to content

Commit b5c9dae

Browse files
michael-websterwebsterclaude
authored
Make workdir consistent with the sidecar structure. (#359)
* Make workdir consistent with the sidecar structure. Chunk sidecars use /home/user as the home dir, this makes the command logic reference that known location instead of relative paths. * Address review: validate workspace path depth, derive sidecar home from env - ResolveWorkspace now returns (string, error) and errors when repo is empty with no saved workspace, preventing rm -rf on the bare home dir - Replace hardcoded /home/user constant with sidecarHome() which reads CHUNK_SIDECAR_HOME env var, falling back to /home/user - Update callers in sync.go and validate.go - Add tests for the empty-repo error and env var override Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: webster <michael@webster.fyi> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ae6ff92 commit b5c9dae

5 files changed

Lines changed: 57 additions & 16 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ PLANS.md
4242

4343
# unikraft
4444
.unikraft/
45+
test-fetch-on-stale-sidecar.log

internal/cmd/sidecar.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ func newSidecarSyncCmd() *cobra.Command {
454454

455455
cmd.Flags().StringVar(&sidecarID, "sidecar-id", "", "Sidecar ID (defaults to active sidecar)")
456456
cmd.Flags().StringVar(&identityFile, "identity-file", "", "SSH identity file")
457-
cmd.Flags().StringVar(&workdir, "workdir", "", "Destination path on sidecar (auto-detected as ~/workspace/<repo> when omitted)")
457+
cmd.Flags().StringVar(&workdir, "workdir", "", "Destination path on sidecar (defaults to /home/user/<repo> when omitted)")
458458

459459
return cmd
460460
}

internal/cmd/validate.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func newValidateCmd() *cobra.Command {
113113
cmd.Flags().StringVar(&opts.sidecarID, "sidecar-id", "", "Sidecar ID for remote execution")
114114
cmd.Flags().StringVar(&opts.orgID, "org-id", "", "Organization ID (used when creating a new sidecar)")
115115
cmd.Flags().StringVar(&opts.identityFile, "identity-file", "", "SSH identity file (uses ssh-agent or ~/.ssh/chunk_ai when omitted)")
116-
cmd.Flags().StringVar(&opts.workdir, "workdir", "", "Working directory on sidecar (reads from sidecar.json, defaults to ./workspace)")
116+
cmd.Flags().StringVar(&opts.workdir, "workdir", "", "Working directory on sidecar (reads from sidecar.json, defaults to /home/user/<repo>)")
117117
cmd.Flags().BoolVar(&opts.dryRun, "dry-run", false, "Show commands without executing")
118118
cmd.Flags().BoolVar(&opts.list, "list", false, "List all configured commands")
119119
cmd.Flags().BoolVar(&opts.jsonOut, "json", false, "Output as JSON (only applies with --list)")
@@ -435,7 +435,10 @@ func openSSHSession(ctx context.Context, client *circleci.Client, sidecarID, ide
435435
}
436436
cwd, _ := os.Getwd()
437437
_, repo, _ := gitremote.DetectOrgAndRepo(cwd)
438-
dest := sidecar.ResolveWorkspace(ctx, workdir, repo)
438+
dest, err := sidecar.ResolveWorkspace(ctx, workdir, repo)
439+
if err != nil {
440+
return nil, "", &userError{msg: "Could not determine workspace path.", err: err}
441+
}
439442
merged := hostForwardEnv(rc.CircleCIToken)
440443
if merged == nil {
441444
merged = make(map[string]string, len(envVars))

internal/sidecar/active_test.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ func TestResolveWorkspaceCLIFlagWins(t *testing.T) {
223223
ctx := context.Background()
224224
assert.NilError(t, SaveActive(ctx, ActiveSidecar{SidecarID: "sb-1", Workspace: "/workspace/saved"}))
225225

226-
got := ResolveWorkspace(ctx, "/workspace/override", "myrepo")
226+
got, err := ResolveWorkspace(ctx, "/workspace/override", "myrepo")
227+
assert.NilError(t, err)
227228
assert.Equal(t, got, "/workspace/override")
228229
}
229230

@@ -235,7 +236,8 @@ func TestResolveWorkspaceSidecarFallback(t *testing.T) {
235236
ctx := context.Background()
236237
assert.NilError(t, SaveActive(ctx, ActiveSidecar{SidecarID: "sb-1", Workspace: "/workspace/saved"}))
237238

238-
got := ResolveWorkspace(ctx, "", "myrepo")
239+
got, err := ResolveWorkspace(ctx, "", "myrepo")
240+
assert.NilError(t, err)
239241
assert.Equal(t, got, "/workspace/saved")
240242
}
241243

@@ -244,8 +246,30 @@ func TestResolveWorkspaceDefaultFallback(t *testing.T) {
244246
t.Chdir(dir)
245247
setupXDGData(t)
246248

247-
got := ResolveWorkspace(context.Background(), "", "myrepo")
248-
assert.Equal(t, got, "./workspace/myrepo")
249+
got, err := ResolveWorkspace(context.Background(), "", "myrepo")
250+
assert.NilError(t, err)
251+
assert.Equal(t, got, "/home/user/myrepo")
252+
}
253+
254+
func TestResolveWorkspaceEmptyRepoErrors(t *testing.T) {
255+
dir := t.TempDir()
256+
t.Chdir(dir)
257+
setupXDGData(t)
258+
259+
_, err := ResolveWorkspace(context.Background(), "", "")
260+
assert.ErrorContains(t, err, "repo name is empty")
261+
}
262+
263+
func TestResolveWorkspaceSidecarHomeEnvVar(t *testing.T) {
264+
dir := t.TempDir()
265+
t.Chdir(dir)
266+
setupXDGData(t)
267+
268+
t.Setenv("CHUNK_SIDECAR_HOME", "/home/runner")
269+
270+
got, err := ResolveWorkspace(context.Background(), "", "myrepo")
271+
assert.NilError(t, err)
272+
assert.Equal(t, got, "/home/runner/myrepo")
249273
}
250274

251275
func TestSidecarFileNameCases(t *testing.T) {

internal/sidecar/sync.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,31 @@ import (
1414
"github.com/CircleCI-Public/chunk-cli/internal/iostream"
1515
)
1616

17-
const workspaceDir = "./workspace"
17+
// sidecarHome returns the base home directory on the sidecar. It reads
18+
// CHUNK_SIDECAR_HOME so the default "/home/user" can be overridden when the
19+
// image uses a different OS user.
20+
func sidecarHome() string {
21+
if h := os.Getenv("CHUNK_SIDECAR_HOME"); h != "" {
22+
return h
23+
}
24+
return "/home/user"
25+
}
1826

1927
// ResolveWorkspace determines the workspace path. Priority:
20-
// 1. CLI --workdir flag 2. sidecar.json workspace 3. default ./workspace/<repo>.
21-
func ResolveWorkspace(ctx context.Context, cliWorkdir, repo string) string {
28+
// 1. CLI --workdir flag 2. sidecar.json workspace 3. default <sidecarHome>/<repo>.
29+
// Returns an error if no repo-specific path can be determined (repo empty and no
30+
// saved workspace), because the bare home dir is not safe to pass to rm -rf.
31+
func ResolveWorkspace(ctx context.Context, cliWorkdir, repo string) (string, error) {
2232
if cliWorkdir != "" {
23-
return cliWorkdir
33+
return cliWorkdir, nil
2434
}
2535
if active, err := LoadActive(ctx); err == nil && active != nil && active.Workspace != "" {
26-
return active.Workspace
36+
return active.Workspace, nil
2737
}
2838
if repo == "" {
29-
return workspaceDir
39+
return "", fmt.Errorf("sync: cannot determine workspace: repo name is empty and no workspace is saved")
3040
}
31-
return workspaceDir + "/" + repo
41+
return sidecarHome() + "/" + repo, nil
3242
}
3343

3444
// persistWorkspace saves the resolved workspace back to the sidecar file if it
@@ -48,7 +58,7 @@ func persistWorkspace(ctx context.Context, workspace string) error {
4858
// Sync synchronises local changes to a sidecar over SSH.
4959
// It ensures the workspace base exists, clones the repo into workdir if absent,
5060
// then resets to the remote base and applies a patch of local changes.
51-
// workdir overrides the destination path; defaults to /workspace/<repo>.
61+
// workdir overrides the destination path; defaults to /home/user/<repo>.
5262
func Sync(ctx context.Context,
5363
client *circleci.Client, sidecarID, identityFile, authSock, workdir string, status iostream.StatusFunc) error {
5464

@@ -67,7 +77,10 @@ func Sync(ctx context.Context,
6777
return &NoOriginRemoteError{Err: err}
6878
}
6979

70-
repoPath := ResolveWorkspace(ctx, workdir, repo)
80+
repoPath, err := ResolveWorkspace(ctx, workdir, repo)
81+
if err != nil {
82+
return err
83+
}
7184

7285
if err := persistWorkspace(ctx, repoPath); err != nil {
7386
status(iostream.LevelWarn, fmt.Sprintf("Could not save workspace: %v", err))

0 commit comments

Comments
 (0)