docs/plans/2026-01-29-remove-system-bot-user-design.md
Date: 2026-01-29 Status: Approved
Bytebase currently uses a special "SystemBot" user (ID=1, email='[email protected]') to represent system-generated actions. This includes:
This SystemBot appears in the UI alongside real users, which confuses users. It's unclear whether "Bytebase [email protected]" is a real user, a support account, or something else.
Remove the SystemBot concept entirely. System-generated actions will be clearly distinguished from user actions:
creator fields will be NULL for system operations"" used as sentinel for system operationsOnly two tables need schema changes to support NULL creators:
issue_comment (creator) - System creates comments for status changestask_run (creator) - System scheduler creates/updates task runsAll other tables with creator columns remain unchanged - they only contain user-created records.
-- Make issue_comment.creator nullable
ALTER TABLE issue_comment ALTER COLUMN creator DROP NOT NULL;
ALTER TABLE issue_comment DROP CONSTRAINT issue_comment_creator_fkey;
ALTER TABLE issue_comment ADD CONSTRAINT issue_comment_creator_fkey
FOREIGN KEY (creator) REFERENCES principal(email)
ON UPDATE CASCADE ON DELETE SET NULL;
-- Make task_run.creator nullable
ALTER TABLE task_run ALTER COLUMN creator DROP NOT NULL;
ALTER TABLE task_run DROP CONSTRAINT task_run_creator_fkey;
ALTER TABLE task_run ADD CONSTRAINT task_run_creator_fkey
FOREIGN KEY (creator) REFERENCES principal(email)
ON UPDATE CASCADE ON DELETE SET NULL;
Note: This minimal change reduces migration risk - we only modify tables that actually need it.
-- Step 1: Make columns nullable and update FK constraints
ALTER TABLE issue_comment ALTER COLUMN creator DROP NOT NULL;
ALTER TABLE issue_comment DROP CONSTRAINT issue_comment_creator_fkey;
ALTER TABLE issue_comment ADD CONSTRAINT issue_comment_creator_fkey
FOREIGN KEY (creator) REFERENCES principal(email)
ON UPDATE CASCADE ON DELETE SET NULL;
ALTER TABLE task_run ALTER COLUMN creator DROP NOT NULL;
ALTER TABLE task_run DROP CONSTRAINT task_run_creator_fkey;
ALTER TABLE task_run ADD CONSTRAINT task_run_creator_fkey
FOREIGN KEY (creator) REFERENCES principal(email)
ON UPDATE CASCADE ON DELETE SET NULL;
-- Step 2: Convert all existing SystemBot records to NULL
UPDATE issue_comment SET creator = NULL
WHERE creator = '[email protected]';
UPDATE task_run SET creator = NULL
WHERE creator = '[email protected]';
-- Step 3: Delete the SystemBot principal row
DELETE FROM principal WHERE id = 1 AND email = '[email protected]';
backend/migrator/migration/X.XX/0001##remove_system_bot.sqlbackend/migrator/migration/LATEST.sql (remove SystemBot INSERT, update table definitions)backend/migrator/migrator_test.go TestLatestVersionUpdate store methods to handle empty string as system sentinel:
// CreateIssueComments - update to convert "" to NULL
func (s *Store) CreateIssueComments(ctx context.Context, creator string, create *IssueCommentMessage) (*IssueCommentMessage, error) {
var creatorPtr *string
if creator == "" {
creatorPtr = nil // NULL for system
} else {
creatorPtr = &creator
}
// Use creatorPtr in INSERT query
// ...
}
// CreatePendingTaskRuns - update similarly
// UpdateTaskRunStatus - update TaskRunStatusPatch.Updater handling
Replace all common.SystemBotEmail with "":
// issue_service.go - Issue status change comments
s.store.CreateIssueComments(ctx, "", &store.IssueCommentMessage{...})
// rollout_service.go - Auto-rollout task runs
s.CreatePendingTaskRuns(ctx, "", create)
s.CreateIssueComments(ctx, "", &store.IssueCommentMessage{...})
// running_scheduler.go & pending_scheduler.go - Task run updates
s.store.UpdateTaskRunStatus(ctx, &store.TaskRunStatusPatch{
ID: taskRun.ID,
Updater: "", // System update
Status: storepb.TaskRun_AVAILABLE,
})
// user_service.go - Remove SystemBot special case
// DELETE this entire block:
if email == common.SystemBotEmail {
v1User, err := convertToUser(ctx, s.iamManager, store.SystemBotUser)
// ...
}
// issue_service.go & approval/runner.go - Remove fallback to SystemBotUser
// When creator fetch fails, handle as deleted/unknown user, not SystemBot
// backend/common/const.go - Remove:
// - SystemBotID
// - SystemBotEmail
// backend/store/principal.go - Remove:
// - SystemBotUser variable
// - UpdateUser SystemBotID check
// frontend/src/types/common.ts - DELETE:
export const SYSTEM_BOT_ID = 1;
export const SYSTEM_BOT_EMAIL = "[email protected]";
Add helper function to display creator/user names:
// frontend/src/utils/user.ts (or appropriate location)
export const displayUserName = (user: User | null | undefined): string => {
if (!user || !user.email) {
return t('common.system'); // Returns "System" (internationalized)
}
return user.name || user.email;
};
// frontend/src/locales/en-US.json
{
"common": {
"system": "System"
}
}
// frontend/src/locales/zh-CN.json
{
"common": {
"system": "系统"
}
}
// frontend/src/components/Member/MemberDataTable/cells/UserOperationsCell.vue
// REMOVE check for SYSTEM_BOT_USER_NAME - it won't exist anymore
// Any other places checking SYSTEM_BOT_EMAIL or SYSTEM_BOT_ID:
// - Replace with null/undefined checks
// - Use displayUserName() helper
Anywhere displaying creators/updaters (issue lists, task run lists, comments, etc.):
displayUserName() helperStore tests - Update any tests that reference SystemBotUser:
CreateIssueComments with empty string creatorUpdateTaskRunStatus with empty string updaterAPI tests - Update tests that:
Migration tests - Verify:
TestLatestVersion passes with updated versionTask scheduler flow:
Issue status changes:
Migration verification:
[email protected] converted to NULL