From 61d74ee2efec4fea6cda0036436f72f84d693e92 Mon Sep 17 00:00:00 2001 From: Bradley Erickson Date: Wed, 1 Jul 2026 11:53:19 -0700 Subject: [PATCH] fix: assign bot-created projects to the bot's owner When a bot user creates a project, the bot's owner (the human who created the bot) now owns the project instead of the bot itself. The bot retains admin access via an explicit project share. This ensures the human can always see and manage projects their bots create. Co-Authored-By: Claude Opus 4.6 --- pkg/models/project.go | 25 +++++++++++++++++++++++-- pkg/models/project_test.go | 23 +++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/pkg/models/project.go b/pkg/models/project.go index de688348f..623a02811 100644 --- a/pkg/models/project.go +++ b/pkg/models/project.go @@ -1046,8 +1046,17 @@ func CreateProject(s *xorm.Session, project *Project, auth web.Auth, createBackl } project.ID = 0 - project.OwnerID = doer.ID - project.Owner = doer + if doer.IsBot() { + project.OwnerID = doer.BotOwnerID + owner, err := user.GetUserByID(s, doer.BotOwnerID) + if err != nil { + return err + } + project.Owner = owner + } else { + project.OwnerID = doer.ID + project.Owner = doer + } err = checkProjectBeforeUpdateOrDelete(s, project) if err != nil { @@ -1061,6 +1070,18 @@ func CreateProject(s *xorm.Session, project *Project, auth web.Auth, createBackl return } + // Give the bot continued access to the project it created. + if doer.IsBot() { + pu := &ProjectUser{ + ProjectID: project.ID, + Username: doer.Username, + Permission: PermissionAdmin, + } + if err = pu.Create(s, auth); err != nil { + return err + } + } + project.Position = calculateDefaultPosition(project.ID, project.Position) _, err = s.Where("id = ?", project.ID).Update(project) if err != nil { diff --git a/pkg/models/project_test.go b/pkg/models/project_test.go index 47c95cb9b..798c8c4cf 100644 --- a/pkg/models/project_test.go +++ b/pkg/models/project_test.go @@ -79,6 +79,29 @@ func TestProject_CreateOrUpdate(t *testing.T) { "project_view_id": kanbanView.ID, }, false) }) + t.Run("bot project owned by bot owner", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + bot := &user.User{ID: 23, Username: "bot-owner-a-assistant", BotOwnerID: 21} + project := Project{Title: "bot-created"} + err := project.Create(s, bot) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + // The bot's owner should own the project. + db.AssertExists(t, "projects", map[string]interface{}{ + "id": project.ID, + "owner_id": 21, + }, false) + // The bot should retain access via a share. + db.AssertExists(t, "users_projects", map[string]interface{}{ + "user_id": 23, + "project_id": project.ID, + "permission": PermissionAdmin, + }, false) + }) t.Run("kanban view creates To-Do, doing, done buckets", func(t *testing.T) { db.LoadAndAssertFixtures(t) s := db.NewSession()