Skip to content
Open
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- `orc validate` no longer rejects a workspace-level `next_action.cwd` (such as
`"."`) when the feature also has a worktree. Workspace-level stages like
qa-automation intentionally run at the workspace root, which `ResolveCWD`
already blesses; validation now agrees with it.

## [0.5.0] - 2026-06-19

### Added
Expand Down
13 changes: 11 additions & 2 deletions internal/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,11 @@ func ValidateRepos(s *State, workspaceRoot string) error {
}
}

// next_action.cwd must be under a recorded worktree when any worktree is set
// next_action.cwd must resolve to the workspace root or under a recorded
// worktree when any worktree is set. The workspace root is allowed because
// workspace-level stages (e.g. qa-automation) intentionally run there via
// cwd "." even when the feature has a worktree; this mirrors ResolveCWD,
// which maps "."/"" to the workspace root.
hasWorktrees := false
for _, r := range s.Repos {
if r.Worktree != "" {
Expand All @@ -621,8 +625,13 @@ func ValidateRepos(s *State, workspaceRoot string) error {
if !filepath.IsAbs(cwd) {
cwd = filepath.Join(workspaceRoot, cwd)
}
matched := false
cwd = filepath.Clean(cwd)
// A cwd at the workspace root is a valid workspace-level stage.
matched := cwd == filepath.Clean(workspaceRoot)
for _, r := range s.Repos {
if matched {
break
}
if r.Worktree == "" {
continue
}
Expand Down
25 changes: 25 additions & 0 deletions internal/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,31 @@ func TestValidateRepos_CWDNotUnderWorktree(t *testing.T) {
}
}

func TestValidateRepos_CWDWorkspaceRootWithWorktree(t *testing.T) {
root := t.TempDir()
wt := filepath.Join(root, "worktrees", "my-app", "TICKET-1")
if err := os.MkdirAll(wt, 0755); err != nil {
t.Fatal(err)
}
mainPath := filepath.Join(root, "my-app")
if err := os.MkdirAll(mainPath, 0755); err != nil {
t.Fatal(err)
}
// A workspace-level stage (e.g. qa-automation) intentionally runs at the
// workspace root via cwd "." even when the feature has a worktree. ResolveCWD
// blesses this, so validation must accept it rather than reject it as "not
// under any worktree".
s := &state.State{
Repos: map[string]state.Repo{
"my-app": {Main: mainPath, Worktree: wt, Branch: "feature/x"},
},
NextAction: state.NextAction{CWD: "."},
}
if err := state.ValidateRepos(s, root); err != nil {
t.Errorf("expected nil for workspace-root cwd with worktree, got %v", err)
}
}

func TestValidateRepos_CWDSkippedWhenNoWorktrees(t *testing.T) {
root := t.TempDir()
// Repos set but no worktrees recorded — cwd check should be skipped
Expand Down