From 295cda5787202fce7dad8ef33a6da0596541acbd Mon Sep 17 00:00:00 2001 From: Harshit Singh Bhandari Date: Fri, 12 Jun 2026 16:26:42 +0530 Subject: [PATCH] fix: forward project permissions to agent launches (#198) --- backend/internal/session_manager/manager.go | 7 ++- .../internal/session_manager/manager_test.go | 57 ++++++++++++++++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/backend/internal/session_manager/manager.go b/backend/internal/session_manager/manager.go index 2d706a6c4..9a563e96f 100644 --- a/backend/internal/session_manager/manager.go +++ b/backend/internal/session_manager/manager.go @@ -216,13 +216,15 @@ func (m *Manager) Spawn(ctx context.Context, cfg ports.SpawnConfig) (domain.Sess m.markSpawnFailedTerminated(ctx, id) return domain.SessionRecord{}, fmt.Errorf("spawn %s: %w", id, err) } + agentConfig := effectiveAgentConfig(cfg.Kind, project.Config) argv, err := agent.GetLaunchCommand(ctx, ports.LaunchConfig{ SessionID: string(id), WorkspacePath: ws.Path, Prompt: prompt, SystemPrompt: systemPrompt, IssueID: string(cfg.IssueID), - Config: effectiveAgentConfig(cfg.Kind, project.Config), + Config: agentConfig, + Permissions: agentConfig.Permissions, }) if err != nil { _ = m.workspace.Destroy(ctx, ws) @@ -823,7 +825,7 @@ func restoreArgv(ctx context.Context, agent ports.Agent, id domain.SessionID, wo WorkspacePath: workspacePath, Metadata: map[string]string{ports.MetadataKeyAgentSessionID: meta.AgentSessionID}, } - cmd, ok, err := agent.GetRestoreCommand(ctx, ports.RestoreConfig{Session: ref, Config: agentConfig}) + cmd, ok, err := agent.GetRestoreCommand(ctx, ports.RestoreConfig{Session: ref, Config: agentConfig, Permissions: agentConfig.Permissions}) if err != nil { return nil, fmt.Errorf("restore command: %w", err) } @@ -835,6 +837,7 @@ func restoreArgv(ctx context.Context, agent ports.Agent, id domain.SessionID, wo WorkspacePath: workspacePath, Prompt: meta.Prompt, Config: agentConfig, + Permissions: agentConfig.Permissions, }) if err != nil { return nil, fmt.Errorf("launch command: %w", err) diff --git a/backend/internal/session_manager/manager_test.go b/backend/internal/session_manager/manager_test.go index 779340fa6..cc7832e2a 100644 --- a/backend/internal/session_manager/manager_test.go +++ b/backend/internal/session_manager/manager_test.go @@ -154,8 +154,9 @@ func (fakeAgents) Agent(domain.AgentHarness) (ports.Agent, bool) { return fakeAg // session manager resolved and forwarded a project's agent config. type recordingAgent struct { fakeAgent - lastConfig ports.AgentConfig - lastLaunch ports.LaunchConfig + lastConfig ports.AgentConfig + lastLaunch ports.LaunchConfig + lastRestore ports.RestoreConfig } func (a *recordingAgent) GetLaunchCommand(_ context.Context, cfg ports.LaunchConfig) ([]string, error) { @@ -166,6 +167,7 @@ func (a *recordingAgent) GetLaunchCommand(_ context.Context, cfg ports.LaunchCon func (a *recordingAgent) GetRestoreCommand(_ context.Context, cfg ports.RestoreConfig) ([]string, bool, error) { a.lastConfig = cfg.Config + a.lastRestore = cfg return []string{"resume"}, true, nil } @@ -497,6 +499,57 @@ func TestSpawn_DefaultsBranchFromSessionID(t *testing.T) { } } +func TestSpawn_ForwardsResolvedAgentConfigPermissions(t *testing.T) { + st := newFakeStore() + st.projects["mer"] = domain.ProjectRecord{ID: "mer", Config: domain.ProjectConfig{ + AgentConfig: domain.AgentConfig{Permissions: domain.PermissionModeAuto}, + Worker: domain.RoleOverride{AgentConfig: domain.AgentConfig{Permissions: domain.PermissionModeBypassPermissions}}, + }} + agent := &recordingAgent{} + lookPath := func(string) (string, error) { return "/bin/true", nil } + m := New(Deps{Runtime: &fakeRuntime{}, Agents: singleAgent{agent: agent}, Workspace: &fakeWorkspace{}, Store: st, Messenger: &fakeMessenger{}, Lifecycle: &fakeLCM{store: st}, LookPath: lookPath}) + + _, err := m.Spawn(ctx, ports.SpawnConfig{ProjectID: "mer", Kind: domain.KindWorker}) + if err != nil { + t.Fatal(err) + } + + if agent.lastLaunch.Config.Permissions != domain.PermissionModeBypassPermissions { + t.Fatalf("launch config permissions = %q, want bypass", agent.lastLaunch.Config.Permissions) + } + if agent.lastLaunch.Permissions != domain.PermissionModeBypassPermissions { + t.Fatalf("launch permissions = %q, want bypass", agent.lastLaunch.Permissions) + } +} + +func TestRestore_ForwardsResolvedAgentConfigPermissions(t *testing.T) { + st := newFakeStore() + st.projects["mer"] = domain.ProjectRecord{ID: "mer", Config: domain.ProjectConfig{ + AgentConfig: domain.AgentConfig{Permissions: domain.PermissionModeBypassPermissions}, + }} + st.sessions["mer-1"] = domain.SessionRecord{ + ID: "mer-1", + ProjectID: "mer", + Kind: domain.KindWorker, + IsTerminated: true, + Metadata: domain.SessionMetadata{Branch: "ao/mer-1", WorkspacePath: "/tmp/ws", AgentSessionID: "native-1"}, + } + agent := &recordingAgent{} + m := New(Deps{Runtime: &fakeRuntime{}, Agents: singleAgent{agent: agent}, Workspace: &fakeWorkspace{}, Store: st, Messenger: &fakeMessenger{}, Lifecycle: &fakeLCM{store: st}, LookPath: func(string) (string, error) { return "/bin/true", nil }}) + + _, err := m.Restore(ctx, "mer-1") + if err != nil { + t.Fatal(err) + } + + if agent.lastRestore.Config.Permissions != domain.PermissionModeBypassPermissions { + t.Fatalf("restore config permissions = %q, want bypass", agent.lastRestore.Config.Permissions) + } + if agent.lastRestore.Permissions != domain.PermissionModeBypassPermissions { + t.Fatalf("restore permissions = %q, want bypass", agent.lastRestore.Permissions) + } +} + func TestSpawnWorker_AppendsActiveOrchestratorContact(t *testing.T) { st := newFakeStore() st.num = 1