Scope the fallback check per repository
apps/backend/internal/orchestrator/executor/executor_environment_reuse.go ↗The function now checks per repository whether a branch-scoped row exists, so the legacy empty-branch row only matches when the same repository has no scoped row.
Before and after
matches := 0
expectedBranchSlug := launchRepoBranchIdentitySlug(spec)
allowLegacyEmptyBranch := expectedBranchSlug != "" && !hasBranchScopedEnvironmentRepoRows(rows)
allowLegacyEmptyBranch := expectedBranchSlug != "" && !repositoryHasBranchScopedRepoRow(rows, spec.RepositoryID)
for _, row := range rows {
branchMatches := worktree.SanitizeBranchSlug(row.BranchSlug) == expectedBranchSlug
New per-repository branch-scope helper
apps/backend/internal/orchestrator/executor/executor_environment_reuse.go ↗The helper reports branch scope for one repository without requiring a worktree ID, which lets local executor rows count as scoped.
New helper
// repositoryHasBranchScopedRepoRow reports whether repos contains a row for
// repositoryID with a non-empty sanitized branch slug. WorktreeID is not
// required because local executor rows can be branch-scoped without a worktree.
func repositoryHasBranchScopedRepoRow(repos []*models.TaskEnvironmentRepo, repositoryID string) bool {
for _, repo := range repos {
if repo.RepositoryID == repositoryID && worktree.SanitizeBranchSlug(repo.BranchSlug) != "" {
return true
}
}
return false
}
Guard test for scoped plus legacy rows
apps/backend/internal/orchestrator/executor/executor_environment_reuse_inventory_test.go ↗The test proves the guard now attaches when a repository has both a main row and a legacy empty-branch row.
Regression test
func TestValidateReuseEnvironmentInventory_ScopedBranchPlusLegacyEmptyRowAttaches(t *testing.T) {
repo := newMockRepository()
repo.taskRepositories["task-repo-1"] = &models.TaskRepository{ID: "task-repo-1", TaskID: "task-1", RepositoryID: "repo-1"}
e := newTestExecutor(t, &mockAgentManager{}, repo)
req := &LaunchAgentRequest{
TaskID: "task-1",
WorkspaceReuseRequired: true,
Repositories: []RepoSpec{
{RepositoryID: "repo-1", BranchIdentitySlug: "main"},
},
}
env := &models.TaskEnvironment{ID: "env-1"}
repo.taskEnvironmentRepos[env.ID] = []*models.TaskEnvironmentRepo{
{RepositoryID: "repo-1", BranchSlug: "main", WorktreeID: ""},
{RepositoryID: "repo-1", BranchSlug: "", WorktreeID: "worktree-legacy"},
}
if err := e.validateReuseEnvironmentInventory(context.Background(), req, env); err != nil {
t.Fatalf("validateReuseEnvironmentInventory() = %v, want nil", err)
}
}
Unit tests for the match logic
apps/backend/internal/orchestrator/executor/executor_environment_test.go ↗The tests verify that a scoped row suppresses the fallback for its own repository but not for other repositories.
Suppress fallback for same repo
func TestCanonicalInventoryMatches_ScopedLocalBranchSuppressesLegacyFallback(t *testing.T) {
spec := RepoSpec{RepositoryID: "repo-1", BranchIdentitySlug: "main"}
rows := []*models.TaskEnvironmentRepo{
{RepositoryID: "repo-1", BranchSlug: "main", WorktreeID: ""},
{RepositoryID: "repo-1", BranchSlug: "", WorktreeID: "worktree-legacy"},
}
if got := canonicalInventoryMatches(spec, rows, false); got != 1 {
t.Fatalf("canonicalInventoryMatches() = %d, want 1 (scoped row must suppress legacy fallback)", got)
}
}
Other repo does not suppress
func TestCanonicalInventoryMatches_ScopedRowOnOtherRepoDoesNotSuppressFallbackForThisRepo(t *testing.T) {
spec := RepoSpec{RepositoryID: "repo-1", BranchIdentitySlug: "main"}
rows := []*models.TaskEnvironmentRepo{
{RepositoryID: "repo-1", BranchSlug: "", WorktreeID: "worktree-legacy"},
{RepositoryID: "repo-2", BranchSlug: "main", WorktreeID: "worktree-scoped"},
}
if got := canonicalInventoryMatches(spec, rows, true); got != 1 {
t.Fatalf("canonicalInventoryMatches() = %d, want 1 (other repo's scoped row must not suppress repo-1 fallback)", got)
}
}
Spec clarifies per-repository fallback
docs/specs/tasks/system-design/additional-session-workspace-reuse.md ↗The spec now states that the empty-branch fallback is per repository and that branch scope does not need a worktree ID.
Spec update
The canonical inventory match for a slot is scoped per repository. The legacy
empty-branch fallback stands in for a repository's branch slot only when that
repository has no row carrying a non-empty branch slug; branch scoping does not
depend on a worktree identifier, so a local-executor row with a branch and no
worktree ID is already branch-scoped and suppresses the fallback for its own
repository. A slot therefore never matches more than one row.