* All tasks are complete. Here's a summary of the refactoring that was done:
The `task_attempts` table has been refactored into `workspaces` and `sessions`:
- Created new migration `20251216142123_refactor_task_attempts_to_workspaces_sessions.sql`
- Renamed `task_attempts` table to `workspaces` (removed `executor` column)
- Created new `sessions` table with `(id, workspace_id, executor, created_at, updated_at)`
- Renamed `executor_sessions` to `coding_agent_turns` (dropped redundant `task_attempt_id`)
- Changed `execution_processes.task_attempt_id` to `session_id`
- Changed `merges.task_attempt_id` to `workspace_id`
- Changed `tasks.parent_task_attempt` to `parent_workspace_id`
- Renamed `crates/db/src/models/task_attempt.rs` → `workspace.rs`
- Created new `crates/db/src/models/session.rs`
- Renamed `executor_session.rs` → `coding_agent_turn.rs`
- Updated `execution_process.rs`:
- Changed `task_attempt_id` field to `session_id`
- Added workspace-level query methods (`find_latest_by_workspace_and_run_reason`, `latest_executor_profile_for_workspace`, `find_latest_external_session_id_by_workspace`, `find_running_dev_servers_by_workspace`, `has_running_non_dev_server_processes_for_workspace`)
- Updated `merge.rs`: Changed `task_attempt_id` to `workspace_id`
- Updated `task.rs`: Changed `parent_task_attempt` to `parent_workspace_id`
- Updated `container.rs`: Changed `start_execution` to take 4 parameters (workspace, session, action, reason)
- Updated `events/`: Changed event types from `TaskAttempt` to `Workspace`
- Updated `pr_monitor.rs`: Changed to use `Workspace` instead of `TaskAttempt`
- Updated `workspace_manager.rs`: Added support for `DbWorkspace` alias
- Updated `task_attempts.rs`: All handlers now create/use sessions for execution processes
- Updated `tasks.rs`: Removed `executor` from `CreateWorkspace`, fixed error type
- Updated setup helpers (codex, cursor, gh_cli) to create sessions
- Updated `error.rs`: Added `SessionError` handling
Cleanup script changes for task attempt 2f672d73-cb46-4515-b1c7-3da8f4402858
Done. All 3 `sqlx::query_scalar()` function calls have been converted to `sqlx::query_scalar!()` macros:
1. `has_running_non_dev_server_processes()` - lines 332-341
2. `has_running_non_dev_server_processes_for_workspace()` - lines 350-361
3. `find_prev_after_head_commit()` - lines 619-634
Key changes:
- Converted `?` placeholders to `$1, $2, $3` positional parameters (sqlx macro style)
- Added `as "count!: i64"` type annotation for COUNT queries
- Used `.flatten()` for the nullable `after_head_commit` column to avoid `Option<Option<String>>`
The one in `crates/remote/src/db/mod.rs` uses dynamic SQL for PostgreSQL's `ALTER ROLE` and cannot be converted to a macro.
Done. Removed the 4 unused functions from `execution_process.rs`:
- `find_running_dev_servers_by_session`
- `has_running_non_dev_server_processes` (the per-session version)
- `delete_by_session_id`
- `latest_executor_profile_for_session`
The workspace-level versions (`has_running_non_dev_server_processes_for_workspace`, `find_running_dev_servers_by_workspace`, `latest_executor_profile_for_workspace`) are still available. Everything builds successfully.
Rewbase
Revert event rename
* All done. Here's a summary of the changes:
Created a `TaskAttempt` helper struct in the backend that maintains backwards compatibility with the frontend:
1. **`crates/db/src/models/workspace.rs`**
- Added `TaskAttempt` struct that combines `Workspace` fields + `executor` from the latest `Session`
- Added `TaskAttempt::from_workspace()` to convert a `Workspace` by fetching its latest session's executor
- Added `TaskAttempt::fetch_all()` for API responses
2. **`crates/db/src/models/task.rs`**
- Changed `TaskRelationships.current_workspace` → `current_attempt: TaskAttempt`
3. **`crates/server/src/routes/task_attempts.rs`**
- Updated `get_task_attempts` to return `Vec<TaskAttempt>`
- Updated `get_task_attempt` to return `TaskAttempt`
- Updated `create_task_attempt` to return `TaskAttempt`
4. **`crates/server/src/bin/generate_types.rs`**
- Added `TaskAttempt::decl()` to the type generation list
Fixed references from `parent_task_attempt` to `parent_workspace_id` in:
- `TaskCard.tsx`
- `NoServerContent.tsx`
- `TaskPanel.tsx`
- `TaskFormDialog.tsx`
- `CreateAttemptDialog.tsx`
- `useTaskMutations.ts`
- `ProjectTasks.tsx`
- Frontend now gets `TaskAttempt` type with the `executor` field
- The `TaskRelationships.current_attempt` field provides `TaskAttempt` with executor
- Backend still uses `Workspace` + `Session` internally
- All type checks pass
Fixed. The issue was that the frontend was sending `task_attempt_id` as the query parameter, but the backend expected `workspace_id`. Now the execution processes WebSocket stream should work correctly and the "Loading History" should resolve.
* All done. Here's a summary of the changes:
1. **Added sessions endpoint** (`GET /api/sessions?workspace_id={uuid}`)
- Created `crates/server/src/routes/sessions.rs`
- Registered in `crates/server/src/routes/mod.rs`
- Added `Session::decl()` to `generate_types.rs`
2. **Removed TaskAttempt from backend**
- Removed `TaskAttempt` struct and methods from `crates/db/src/models/workspace.rs`
- Changed `TaskRelationships.current_attempt` back to `current_workspace: Workspace` in `task.rs`
- Updated route handlers to return `Workspace` instead of `TaskAttempt`
1. **Created `TaskAttempt` type** in `frontend/src/types/attempt.ts`
- Defines `TaskAttempt = Workspace & { executor: string }`
- Exports `createTaskAttempt()` helper function
2. **Updated `api.ts`**
- Added `sessionsApi` for fetching sessions
- Added `workspaceToTaskAttempt()` helper that fetches sessions and constructs TaskAttempt
- Updated `attemptsApi.getAll()`, `get()`, and `create()` to internally convert Workspace → TaskAttempt
3. **Updated 20+ frontend files** to import `TaskAttempt` from `@/types/attempt` instead of `shared/types`
- Backend returns `Workspace` directly (no executor field)
- Backend has new `/api/sessions` endpoint to fetch sessions by workspace
- Frontend constructs `TaskAttempt` by combining `Workspace` + latest `Session.executor`
- All type checks pass
* The refactoring is complete. Here's a summary of what was done:
- `TaskAttempt` is now just a type alias for `Workspace` (no executor needed for most uses)
- Added `WorkspaceWithSession = Workspace & { executor: string }` for components that need executor
- Added `createWorkspaceWithSession()` helper function
- `attemptsApi.get()`, `getAll()`, `create()` now return `Workspace` directly (as `TaskAttempt`)
- Added `attemptsApi.getWithSession()` - fetches workspace + session to get executor
- Added `sessionsApi.getByWorkspace()` for fetching sessions
- Added `useTaskAttemptWithSession()` - for single attempt with executor
- Added `useTaskAttemptsWithSessions()` - for list of attempts with executor
Files that access `.executor` now use `WorkspaceWithSession`:
- `TaskPanel.tsx` - Uses `useTaskAttemptsWithSessions` and `useTaskAttemptWithSession`
- `NextActionCard.tsx` - Uses `attemptsApi.getWithSession()`
- `UserMessage.tsx` - Receives `WorkspaceWithSession` prop
- `RetryEditorInline.tsx` - Receives `WorkspaceWithSession` prop
- `DisplayConversationEntry.tsx` - Passes `WorkspaceWithSession` to children
- `VirtualizedList.tsx` - Receives `WorkspaceWithSession` prop
- `TaskAttemptPanel.tsx` - Receives `WorkspaceWithSession` prop
- `ProjectTasks.tsx` - Uses `useTaskAttemptWithSession`
- `FullAttemptLogs.tsx` - Uses `useTaskAttemptWithSession`
- `CreateAttemptDialog.tsx` - Uses `useTaskAttemptsWithSessions`
The 32 other files that only use Workspace fields continue to use `TaskAttempt` (which is just `Workspace`) without any changes needed.
Done! I've simplified the migration from 191 lines down to 106 lines by:
1. **Removed workspaces table rebuild** → simple `ALTER TABLE workspaces DROP COLUMN executor`
2. **Removed merges table rebuild** → simple `ALTER TABLE merges RENAME COLUMN task_attempt_id TO workspace_id`
3. **Removed tasks table rebuild** → simple `ALTER TABLE tasks RENAME COLUMN parent_task_attempt TO parent_workspace_id`
4. **Removed attempt_repos rebuild entirely** → FK auto-updates when `task_attempts` is renamed to `workspaces`
The only table rebuilds remaining are for:
- `execution_processes` - because the FK target changes from workspaces to sessions
- `executor_sessions` → `coding_agent_turns` - because we're dropping the `task_attempt_id` column
All done:
- **npm run lint** - passed (fixed 4 unused import errors)
- **cargo build** - passed
- **npm run build** - passed
- **tsc --noEmit** - passed
Prep db
* Done. I've renamed `TaskAttempt` to `Workspace` in all places where it was used as a type annotation. Here's the summary:
- `types/attempt.ts` - Removed `export type TaskAttempt = Workspace;`
**API Layer:**
- `lib/api.ts` - Changed return types from `TaskAttempt` to `Workspace`
**Hooks:**
- `hooks/useTaskAttempts.ts` - `TaskAttempt[]` → `Workspace[]`
- `hooks/useAttemptCreation.ts` - `TaskAttempt` → `Workspace`
- `hooks/useConversationHistory.ts` - `TaskAttempt` → `Workspace`
- `hooks/useAttempt.ts` - `TaskAttempt` → `Workspace`
**Components:**
- `contexts/ClickedElementsProvider.tsx`
- `components/DiffCard.tsx`
- `components/panels/AttemptHeaderActions.tsx`
- `components/panels/DiffsPanel.tsx`
- `components/tasks/Toolbar/GitOperations.tsx`
- `components/dialogs/tasks/GitActionsDialog.tsx`
- `components/dialogs/tasks/ViewRelatedTasksDialog.tsx`
- `components/dialogs/tasks/CreatePRDialog.tsx`
- `components/ui/actions-dropdown.tsx`
**Type Definitions:**
- `types/modal-args.d.ts`
- `types/modals.ts`
**Pages:**
- `pages/ProjectTasks.tsx`
TypeScript check passes with no errors.
Cleanup script changes for task attempt 2f672d73-cb46-4515-b1c7-3da8f4402858
* The Session type now has `executor: string | null`, which is correct. All changes are complete:
1. **Migration**: `executor TEXT` (no `NOT NULL`)
2. **Rust model**: `executor: Option<String>`
3. **sqlx queries**: Using just `executor` without type annotation (sqlx infers nullable correctly)
4. **All usages**: Wrapped string values in `Some()` where executor is set
5. **TypeScript types**: `executor: string | null`
Everything builds and lints successfully.
Done. Removed `Session::parent_workspace` and its unused `Workspace` import.
* Done. The migration has been updated with the sqlx workaround:
1. Added `COMMIT TRANSACTION;` after `PRAGMA foreign_keys = OFF;` to end sqlx's auto-transaction
2. Added `BEGIN TRANSACTION;` to start our own controlled transaction
3. Added `PRAGMA foreign_key_check;` before committing to verify FK integrity
4. Added `COMMIT;` to commit our transaction
5. Added final `BEGIN TRANSACTION;` for sqlx to close gracefully
This ensures the `PRAGMA foreign_keys = OFF` actually takes effect, preventing potential cascade deletes of `execution_process_logs` when `DROP TABLE execution_processes` runs on existing databases with data.
Fixed. The migration now has the correct order:
1. `COMMIT TRANSACTION;` - ends sqlx's auto-transaction
2. `PRAGMA foreign_keys = OFF;` - now takes effect (outside transaction)
3. `BEGIN TRANSACTION;` - starts our controlled transaction
This ensures foreign keys are actually disabled before the table operations, preventing potential cascade deletes of `execution_process_logs` when `DROP TABLE execution_processes` runs.
Done. The migration now only disables foreign keys for the section that needs it (steps 7-8, the table rebuilds with DROP TABLE). Steps 1-6 run in sqlx's normal auto-transaction with FK enforcement on.
Fix slow statement (vibe-kanban b695d0ad)
i think we are missing an index on something, seeing these logs:
2025-12-17T15:23:25.297852Z WARN sqlx::query: slow statement: execution time exceeded alert threshold *summary*="SELECT t.id AS \\"id!: …" *db.statement*="\\n\\nSELECT\\n t.id AS \\"id!: Uuid\\",\\n t.project\_id AS \\"project\_id!: Uuid\\",\\n t.title,\\n t.description,\\n t.status AS \\"status!: TaskStatus\\",\\n t.parent\_workspace\_id AS \\"parent\_workspace\_id: Uuid\\",\\n t.shared\_task\_id AS \\"shared\_task\_id: Uuid\\",\\n t.created\_at AS \\"created\_at!: DateTime<Utc>\\",\\n t.updated\_at AS \\"updated\_at!: DateTime<Utc>\\",\\n\\n CASE WHEN EXISTS (\\n SELECT 1\\n FROM workspaces w\\n JOIN sessions s ON s.workspace\_id = w.id\\n JOIN execution\_processes ep ON ep.session\_id = s.id\\n WHERE w.task\_id = t.id\\n AND ep.status = 'running'\\n AND ep.run\_reason IN ('setupscript','cleanupscript','codingagent')\\n LIMIT 1\\n ) THEN 1 ELSE 0 END AS \\"has\_in\_progress\_attempt!: i64\\",\\n\\n CASE WHEN (\\n SELECT ep.status\\n FROM workspaces w\\n JOIN sessions s ON s.workspace\_id = w.id\\n JOIN execution\_processes ep ON ep.session\_id = s.id\\n WHERE w.task\_id = t.id\\n AND ep.run\_reason IN ('setupscript','cleanupscript','codingagent')\\n ORDER BY ep.created\_at DESC\\n LIMIT 1\\n ) IN ('failed','killed') THEN 1 ELSE 0 END\\n AS \\"last\_attempt\_failed!: i64\\",\\n\\n ( SELECT s.executor\\n FROM workspaces w\\n JOIN sessions s ON s.workspace\_id = w.id\\n WHERE w.task\_id = t.id\\n ORDER BY s.created\_at DESC\\n LIMIT 1\\n ) AS \\"executor!: String\\"\\n\\nFROM tasks t\\nWHERE t.project\_id = $1\\nORDER BY t.created\_at DESC\\n" *rows\_affected*=0 *rows\_returned*=202 *elapsed*=1.281210542s *elapsed\_secs*=1.281210542 *slow\_threshold*=1s
2025-12-17T15:23:25.350788Z WARN sqlx::query: slow statement: execution time exceeded alert threshold *summary*="SELECT t.id AS \\"id!: …" *db.statement*="\\n\\nSELECT\\n t.id AS \\"id!: Uuid\\",\\n t.project\_id AS \\"project\_id!: Uuid\\",\\n t.title,\\n t.description,\\n t.status AS \\"status!: TaskStatus\\",\\n t.parent\_workspace\_id AS \\"parent\_workspace\_id: Uuid\\",\\n t.shared\_task\_id AS \\"shared\_task\_id: Uuid\\",\\n t.created\_at AS \\"created\_at!: DateTime<Utc>\\",\\n t.updated\_at AS \\"updated\_at!: DateTime<Utc>\\",\\n\\n CASE WHEN EXISTS (\\n SELECT 1\\n FROM workspaces w\\n JOIN sessions s ON s.workspace\_id = w.id\\n JOIN execution\_processes ep ON ep.session\_id = s.id\\n WHERE w.task\_id = t.id\\n AND ep.status = 'running'\\n AND ep.run\_reason IN ('setupscript','cleanupscript','codingagent')\\n LIMIT 1\\n ) THEN 1 ELSE 0 END AS \\"has\_in\_progress\_attempt!: i64\\",\\n\\n CASE WHEN (\\n SELECT ep.status\\n FROM workspaces w\\n JOIN sessions s ON s.workspace\_id = w.id\\n JOIN execution\_processes ep ON ep.session\_id = s.id\\n WHERE w.task\_id = t.id\\n AND ep.run\_reason IN ('setupscript','cleanupscript','codingagent')\\n ORDER BY ep.created\_at DESC\\n LIMIT 1\\n ) IN ('failed','killed') THEN 1 ELSE 0 END\\n AS \\"last\_attempt\_failed!: i64\\",\\n\\n ( SELECT s.executor\\n FROM workspaces w\\n JOIN sessions s ON s.workspace\_id = w.id\\n WHERE w.task\_id = t.id\\n ORDER BY s.created\_at DESC\\n LIMIT 1\\n ) AS \\"executor!: String\\"\\n\\nFROM tasks t\\nWHERE t.project\_id = $1\\nORDER BY t.created\_at DESC\\n" *rows\_affected*=0 *rows\_returned*=202 *elapsed*=1.333812833s *elapsed\_secs*=1.333812833 *slow\_threshold*=1s
2025-12-17T15:23:25.401326Z WARN sqlx::query: slow statement: execution time exceeded alert threshold *summary*="INSERT INTO execution\_processes ( …" *db.statement*="\\n\\nINSERT INTO execution\_processes (\\n id, session\_id, run\_reason, executor\_action,\\n status, exit\_code, started\_at, completed\_at, created\_at, updated\_at\\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\\n" *rows\_affected*=1 *rows\_returned*=0 *elapsed*=1.383690208s *elapsed\_secs*=1.383690208 *slow\_threshold*=1s
* Address feedback (vibe-kanban 81d8dbfa)
A PR opened by your colleague (https://github.com/BloopAI/vibe-kanban/pull/1569)
got some feedback, let's address it.
```gh-comment
{
"id": "2627479232",
"comment_type": "review",
"author": "ggordonhall",
"body": "```suggestion\r\n-- 3. Migrate data: create one session per workspace\r\nINSERT INTO sessions (id, workspace_id, executor, created_at, updated_at)\r\nSELECT gen_random_uuid(), id, executor, created_at, updated_at FROM workspaces;\r\n```\r\n",
"created_at": "2025-12-17T15:17:50Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627479232",
"path": "crates/db/migrations/20251216142123_refactor_task_attempts_to_workspaces_sessions.sql",
"line": 26,
"diff_hunk": "@@ -0,0 +1,121 @@\n+-- Refactor task_attempts into workspaces and sessions\n+-- - Rename task_attempts -> workspaces (keeps workspace-related fields)\n+-- - Create sessions table (executor moves here)\n+-- - Update execution_processes.task_attempt_id -> session_id\n+-- - Rename executor_sessions -> coding_agent_turns (drop redundant task_attempt_id)\n+-- - Rename merges.task_attempt_id -> workspace_id\n+-- - Rename tasks.parent_task_attempt -> parent_workspace_id\n+\n+-- 1. Rename task_attempts to workspaces (FK refs auto-update in schema)\n+ALTER TABLE task_attempts RENAME TO workspaces;\n+\n+-- 2. Create sessions table\n+CREATE TABLE sessions (\n+ id BLOB PRIMARY KEY,\n+ workspace_id BLOB NOT NULL,\n+ executor TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE\n+);\n+\n+CREATE INDEX idx_sessions_workspace_id ON sessions(workspace_id);\n+\n+-- 3. Migrate data: create one session per workspace (using workspace.id as session.id for simplicity)\n+INSERT INTO sessions (id, workspace_id, executor, created_at, updated_at)\n+SELECT id, id, executor, created_at, updated_at FROM workspaces;"
}
```
```gh-comment
{
"id": "2627515578",
"comment_type": "review",
"author": "ggordonhall",
"body": "Why not rename `attempt_repos` to `workspace_repos` here now that `attempt` is a legacy concept?",
"created_at": "2025-12-17T15:27:21Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627515578",
"path": "crates/db/migrations/20251216142123_refactor_task_attempts_to_workspaces_sessions.sql",
"line": 118,
"diff_hunk": "@@ -0,0 +1,121 @@\n+-- Refactor task_attempts into workspaces and sessions\n+-- - Rename task_attempts -> workspaces (keeps workspace-related fields)\n+-- - Create sessions table (executor moves here)\n+-- - Update execution_processes.task_attempt_id -> session_id\n+-- - Rename executor_sessions -> coding_agent_turns (drop redundant task_attempt_id)\n+-- - Rename merges.task_attempt_id -> workspace_id\n+-- - Rename tasks.parent_task_attempt -> parent_workspace_id\n+\n+-- 1. Rename task_attempts to workspaces (FK refs auto-update in schema)\n+ALTER TABLE task_attempts RENAME TO workspaces;\n+\n+-- 2. Create sessions table\n+CREATE TABLE sessions (\n+ id BLOB PRIMARY KEY,\n+ workspace_id BLOB NOT NULL,\n+ executor TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE\n+);\n+\n+CREATE INDEX idx_sessions_workspace_id ON sessions(workspace_id);\n+\n+-- 3. Migrate data: create one session per workspace (using workspace.id as session.id for simplicity)\n+INSERT INTO sessions (id, workspace_id, executor, created_at, updated_at)\n+SELECT id, id, executor, created_at, updated_at FROM workspaces;\n+\n+-- 4. Drop executor column from workspaces\n+ALTER TABLE workspaces DROP COLUMN executor;\n+\n+-- 5. Rename merges.task_attempt_id to workspace_id\n+DROP INDEX idx_merges_task_attempt_id;\n+DROP INDEX idx_merges_open_pr;\n+ALTER TABLE merges RENAME COLUMN task_attempt_id TO workspace_id;\n+CREATE INDEX idx_merges_workspace_id ON merges(workspace_id);\n+CREATE INDEX idx_merges_open_pr ON merges(workspace_id, pr_status)\n+WHERE merge_type = 'pr' AND pr_status = 'open';\n+\n+-- 6. Rename tasks.parent_task_attempt to parent_workspace_id\n+DROP INDEX IF EXISTS idx_tasks_parent_task_attempt;\n+ALTER TABLE tasks RENAME COLUMN parent_task_attempt TO parent_workspace_id;\n+CREATE INDEX idx_tasks_parent_workspace_id ON tasks(parent_workspace_id);\n+\n+-- Steps 7-8 need FK disabled to avoid cascade deletes during DROP TABLE\n+-- sqlx workaround: end auto-transaction to allow PRAGMA to take effect\n+-- https://github.com/launchbadge/sqlx/issues/2085#issuecomment-1499859906\n+COMMIT;\n+\n+PRAGMA foreign_keys = OFF;\n+\n+BEGIN TRANSACTION;\n+\n+-- 7. Update execution_processes to reference session_id instead of task_attempt_id\n+-- (needs rebuild because FK target changes from workspaces to sessions)\n+DROP INDEX IF EXISTS idx_execution_processes_task_attempt_created_at;\n+DROP INDEX IF EXISTS idx_execution_processes_task_attempt_type_created;\n+\n+CREATE TABLE execution_processes_new (\n+ id BLOB PRIMARY KEY,\n+ session_id BLOB NOT NULL,\n+ run_reason TEXT NOT NULL DEFAULT 'setupscript'\n+ CHECK (run_reason IN ('setupscript','codingagent','devserver','cleanupscript')),\n+ executor_action TEXT NOT NULL DEFAULT '{}',\n+ status TEXT NOT NULL DEFAULT 'running'\n+ CHECK (status IN ('running','completed','failed','killed')),\n+ exit_code INTEGER,\n+ dropped INTEGER NOT NULL DEFAULT 0,\n+ started_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ completed_at TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE\n+);\n+\n+-- Since we used workspace.id as session.id, the task_attempt_id values map directly\n+INSERT INTO execution_processes_new (id, session_id, run_reason, executor_action, status, exit_code, dropped, started_at, completed_at, created_at, updated_at)\n+SELECT id, task_attempt_id, run_reason, executor_action, status, exit_code, dropped, started_at, completed_at, created_at, updated_at\n+FROM execution_processes;\n+\n+DROP TABLE execution_processes;\n+ALTER TABLE execution_processes_new RENAME TO execution_processes;\n+\n+-- Recreate execution_processes indexes\n+CREATE INDEX idx_execution_processes_session_id ON execution_processes(session_id);\n+CREATE INDEX idx_execution_processes_status ON execution_processes(status);\n+CREATE INDEX idx_execution_processes_run_reason ON execution_processes(run_reason);\n+\n+-- 8. Rename executor_sessions to coding_agent_turns and drop task_attempt_id\n+-- (needs rebuild to drop the redundant task_attempt_id column)\n+CREATE TABLE coding_agent_turns (\n+ id BLOB PRIMARY KEY,\n+ execution_process_id BLOB NOT NULL,\n+ session_id TEXT,\n+ prompt TEXT,\n+ summary TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (execution_process_id) REFERENCES execution_processes(id) ON DELETE CASCADE\n+);\n+\n+INSERT INTO coding_agent_turns (id, execution_process_id, session_id, prompt, summary, created_at, updated_at)\n+SELECT id, execution_process_id, session_id, prompt, summary, created_at, updated_at\n+FROM executor_sessions;\n+\n+DROP TABLE executor_sessions;\n+\n+-- Recreate coding_agent_turns indexes\n+CREATE INDEX idx_coding_agent_turns_execution_process_id ON coding_agent_turns(execution_process_id);\n+CREATE INDEX idx_coding_agent_turns_session_id ON coding_agent_turns(session_id);\n+\n+-- 9. attempt_repos: no changes needed - FK auto-updated when task_attempts renamed to workspaces"
}
```
```gh-comment
{
"id": "2627694792",
"comment_type": "review",
"author": "ggordonhall",
"body": "Maybe there's a better name than `external_session_id` here? `agent_session_id`? ",
"created_at": "2025-12-17T16:16:24Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627694792",
"path": "crates/db/src/models/execution_process.rs",
"line": 685,
"diff_hunk": "@@ -618,4 +680,34 @@ impl ExecutionProcess {\n )),\n }\n }\n+\n+ /// Find latest coding_agent_turn session_id by workspace (across all sessions)\n+ pub async fn find_latest_external_session_id_by_workspace("
}
```
```gh-comment
{
"id": "2627707446",
"comment_type": "review",
"author": "ggordonhall",
"body": "```suggestion\r\n pub async fn cleanup_workspace(db: &DBService, workspace: &Workspace) {\r\n```",
"created_at": "2025-12-17T16:19:31Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627707446",
"path": "crates/local-deployment/src/container.rs",
"line": 146,
"diff_hunk": "@@ -142,20 +143,20 @@ impl LocalContainerService {\n map.remove(id)\n }\n \n- pub async fn cleanup_attempt_workspace(db: &DBService, attempt: &TaskAttempt) {\n- let Some(container_ref) = &attempt.container_ref else {\n+ pub async fn cleanup_workspace_container(db: &DBService, workspace: &Workspace) {"
}
```
```gh-comment
{
"id": "2627756192",
"comment_type": "review",
"author": "ggordonhall",
"body": "Update `mcp` nomenclature",
"created_at": "2025-12-17T16:31:49Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627756192",
"path": "crates/server/src/mcp/task_server.rs",
"line": 352,
"diff_hunk": "@@ -350,10 +349,9 @@ impl TaskServer {\n project_id: ctx.project.id,\n task_id: ctx.task.id,\n task_title: ctx.task.title,\n- attempt_id: ctx.task_attempt.id,\n- attempt_branch: ctx.task_attempt.branch,\n+ attempt_id: ctx.workspace.id,"
}
```
```gh-comment
{
"id": "2628161769",
"comment_type": "review",
"author": "ggordonhall",
"body": "update, and similar in other events",
"created_at": "2025-12-17T18:27:47Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2628161769",
"path": "crates/server/src/routes/task_attempts.rs",
"line": 1335,
"diff_hunk": "@@ -1295,7 +1332,7 @@ pub async fn start_dev_server(\n serde_json::json!({\n \"task_id\": task.id.to_string(),\n \"project_id\": project.id.to_string(),\n- \"attempt_id\": task_attempt.id.to_string(),\n+ \"attempt_id\": workspace.id.to_string(),"
}
```
```gh-comment
{
"id": "2628194289",
"comment_type": "review",
"author": "ggordonhall",
"body": "Ugly, but we should rename this struct to avoid confusion with the more general concept of a workspace. Ideas...\r\n\r\n- `WorktreeContainer`\r\n...\r\n...\r\n\r\nChatGPT?",
"created_at": "2025-12-17T18:36:30Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2628194289",
"path": "crates/services/src/services/workspace_manager.rs",
"line": 3,
"diff_hunk": "@@ -1,6 +1,6 @@\n use std::path::{Path, PathBuf};\n \n-use db::models::{repo::Repo, task_attempt::TaskAttempt};\n+use db::models::{repo::Repo, workspace::Workspace as DbWorkspace};"
}
```
```gh-comment
{
"id": "2628198036",
"comment_type": "review",
"author": "ggordonhall",
"body": "We could add a BE route for this, and similar hooks where we're aggregating this information on the fly",
"created_at": "2025-12-17T18:37:46Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2628198036",
"path": "frontend/src/hooks/useTaskAttempts.ts",
"line": 43,
"diff_hunk": "@@ -16,10 +20,36 @@ export function useTaskAttempts(taskId?: string, opts?: Options) {\n const enabled = (opts?.enabled ?? true) && !!taskId;\n const refetchInterval = opts?.refetchInterval ?? 5000;\n \n- return useQuery<TaskAttempt[]>({\n+ return useQuery<Workspace[]>({\n queryKey: taskAttemptKeys.byTask(taskId),\n queryFn: () => attemptsApi.getAll(taskId!),\n enabled,\n refetchInterval,\n });\n }\n+\n+/**\n+ * Hook for components that need executor field for all attempts.\n+ * Fetches all attempts and their sessions in parallel.\n+ */\n+export function useTaskAttemptsWithSessions(taskId?: string, opts?: Options) {\n+ const enabled = (opts?.enabled ?? true) && !!taskId;\n+ const refetchInterval = opts?.refetchInterval ?? 5000;\n+\n+ return useQuery<WorkspaceWithSession[]>({\n+ queryKey: taskAttemptKeys.byTaskWithSessions(taskId),\n+ queryFn: async () => {\n+ const attempts = await attemptsApi.getAll(taskId!);\n+ // Fetch sessions for all attempts in parallel"
}
```
* Address feedback (vibe-kanban 81d8dbfa)
A PR opened by your colleague (https://github.com/BloopAI/vibe-kanban/pull/1569)
got some feedback, let's address it.
```gh-comment
{
"id": "2627479232",
"comment_type": "review",
"author": "ggordonhall",
"body": "```suggestion\r\n-- 3. Migrate data: create one session per workspace\r\nINSERT INTO sessions (id, workspace_id, executor, created_at, updated_at)\r\nSELECT gen_random_uuid(), id, executor, created_at, updated_at FROM workspaces;\r\n```\r\n",
"created_at": "2025-12-17T15:17:50Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627479232",
"path": "crates/db/migrations/20251216142123_refactor_task_attempts_to_workspaces_sessions.sql",
"line": 26,
"diff_hunk": "@@ -0,0 +1,121 @@\n+-- Refactor task_attempts into workspaces and sessions\n+-- - Rename task_attempts -> workspaces (keeps workspace-related fields)\n+-- - Create sessions table (executor moves here)\n+-- - Update execution_processes.task_attempt_id -> session_id\n+-- - Rename executor_sessions -> coding_agent_turns (drop redundant task_attempt_id)\n+-- - Rename merges.task_attempt_id -> workspace_id\n+-- - Rename tasks.parent_task_attempt -> parent_workspace_id\n+\n+-- 1. Rename task_attempts to workspaces (FK refs auto-update in schema)\n+ALTER TABLE task_attempts RENAME TO workspaces;\n+\n+-- 2. Create sessions table\n+CREATE TABLE sessions (\n+ id BLOB PRIMARY KEY,\n+ workspace_id BLOB NOT NULL,\n+ executor TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE\n+);\n+\n+CREATE INDEX idx_sessions_workspace_id ON sessions(workspace_id);\n+\n+-- 3. Migrate data: create one session per workspace (using workspace.id as session.id for simplicity)\n+INSERT INTO sessions (id, workspace_id, executor, created_at, updated_at)\n+SELECT id, id, executor, created_at, updated_at FROM workspaces;"
}
```
```gh-comment
{
"id": "2627515578",
"comment_type": "review",
"author": "ggordonhall",
"body": "Why not rename `attempt_repos` to `workspace_repos` here now that `attempt` is a legacy concept?",
"created_at": "2025-12-17T15:27:21Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627515578",
"path": "crates/db/migrations/20251216142123_refactor_task_attempts_to_workspaces_sessions.sql",
"line": 118,
"diff_hunk": "@@ -0,0 +1,121 @@\n+-- Refactor task_attempts into workspaces and sessions\n+-- - Rename task_attempts -> workspaces (keeps workspace-related fields)\n+-- - Create sessions table (executor moves here)\n+-- - Update execution_processes.task_attempt_id -> session_id\n+-- - Rename executor_sessions -> coding_agent_turns (drop redundant task_attempt_id)\n+-- - Rename merges.task_attempt_id -> workspace_id\n+-- - Rename tasks.parent_task_attempt -> parent_workspace_id\n+\n+-- 1. Rename task_attempts to workspaces (FK refs auto-update in schema)\n+ALTER TABLE task_attempts RENAME TO workspaces;\n+\n+-- 2. Create sessions table\n+CREATE TABLE sessions (\n+ id BLOB PRIMARY KEY,\n+ workspace_id BLOB NOT NULL,\n+ executor TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE\n+);\n+\n+CREATE INDEX idx_sessions_workspace_id ON sessions(workspace_id);\n+\n+-- 3. Migrate data: create one session per workspace (using workspace.id as session.id for simplicity)\n+INSERT INTO sessions (id, workspace_id, executor, created_at, updated_at)\n+SELECT id, id, executor, created_at, updated_at FROM workspaces;\n+\n+-- 4. Drop executor column from workspaces\n+ALTER TABLE workspaces DROP COLUMN executor;\n+\n+-- 5. Rename merges.task_attempt_id to workspace_id\n+DROP INDEX idx_merges_task_attempt_id;\n+DROP INDEX idx_merges_open_pr;\n+ALTER TABLE merges RENAME COLUMN task_attempt_id TO workspace_id;\n+CREATE INDEX idx_merges_workspace_id ON merges(workspace_id);\n+CREATE INDEX idx_merges_open_pr ON merges(workspace_id, pr_status)\n+WHERE merge_type = 'pr' AND pr_status = 'open';\n+\n+-- 6. Rename tasks.parent_task_attempt to parent_workspace_id\n+DROP INDEX IF EXISTS idx_tasks_parent_task_attempt;\n+ALTER TABLE tasks RENAME COLUMN parent_task_attempt TO parent_workspace_id;\n+CREATE INDEX idx_tasks_parent_workspace_id ON tasks(parent_workspace_id);\n+\n+-- Steps 7-8 need FK disabled to avoid cascade deletes during DROP TABLE\n+-- sqlx workaround: end auto-transaction to allow PRAGMA to take effect\n+-- https://github.com/launchbadge/sqlx/issues/2085#issuecomment-1499859906\n+COMMIT;\n+\n+PRAGMA foreign_keys = OFF;\n+\n+BEGIN TRANSACTION;\n+\n+-- 7. Update execution_processes to reference session_id instead of task_attempt_id\n+-- (needs rebuild because FK target changes from workspaces to sessions)\n+DROP INDEX IF EXISTS idx_execution_processes_task_attempt_created_at;\n+DROP INDEX IF EXISTS idx_execution_processes_task_attempt_type_created;\n+\n+CREATE TABLE execution_processes_new (\n+ id BLOB PRIMARY KEY,\n+ session_id BLOB NOT NULL,\n+ run_reason TEXT NOT NULL DEFAULT 'setupscript'\n+ CHECK (run_reason IN ('setupscript','codingagent','devserver','cleanupscript')),\n+ executor_action TEXT NOT NULL DEFAULT '{}',\n+ status TEXT NOT NULL DEFAULT 'running'\n+ CHECK (status IN ('running','completed','failed','killed')),\n+ exit_code INTEGER,\n+ dropped INTEGER NOT NULL DEFAULT 0,\n+ started_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ completed_at TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE\n+);\n+\n+-- Since we used workspace.id as session.id, the task_attempt_id values map directly\n+INSERT INTO execution_processes_new (id, session_id, run_reason, executor_action, status, exit_code, dropped, started_at, completed_at, created_at, updated_at)\n+SELECT id, task_attempt_id, run_reason, executor_action, status, exit_code, dropped, started_at, completed_at, created_at, updated_at\n+FROM execution_processes;\n+\n+DROP TABLE execution_processes;\n+ALTER TABLE execution_processes_new RENAME TO execution_processes;\n+\n+-- Recreate execution_processes indexes\n+CREATE INDEX idx_execution_processes_session_id ON execution_processes(session_id);\n+CREATE INDEX idx_execution_processes_status ON execution_processes(status);\n+CREATE INDEX idx_execution_processes_run_reason ON execution_processes(run_reason);\n+\n+-- 8. Rename executor_sessions to coding_agent_turns and drop task_attempt_id\n+-- (needs rebuild to drop the redundant task_attempt_id column)\n+CREATE TABLE coding_agent_turns (\n+ id BLOB PRIMARY KEY,\n+ execution_process_id BLOB NOT NULL,\n+ session_id TEXT,\n+ prompt TEXT,\n+ summary TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (execution_process_id) REFERENCES execution_processes(id) ON DELETE CASCADE\n+);\n+\n+INSERT INTO coding_agent_turns (id, execution_process_id, session_id, prompt, summary, created_at, updated_at)\n+SELECT id, execution_process_id, session_id, prompt, summary, created_at, updated_at\n+FROM executor_sessions;\n+\n+DROP TABLE executor_sessions;\n+\n+-- Recreate coding_agent_turns indexes\n+CREATE INDEX idx_coding_agent_turns_execution_process_id ON coding_agent_turns(execution_process_id);\n+CREATE INDEX idx_coding_agent_turns_session_id ON coding_agent_turns(session_id);\n+\n+-- 9. attempt_repos: no changes needed - FK auto-updated when task_attempts renamed to workspaces"
}
```
```gh-comment
{
"id": "2627694792",
"comment_type": "review",
"author": "ggordonhall",
"body": "Maybe there's a better name than `external_session_id` here? `agent_session_id`? ",
"created_at": "2025-12-17T16:16:24Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627694792",
"path": "crates/db/src/models/execution_process.rs",
"line": 685,
"diff_hunk": "@@ -618,4 +680,34 @@ impl ExecutionProcess {\n )),\n }\n }\n+\n+ /// Find latest coding_agent_turn session_id by workspace (across all sessions)\n+ pub async fn find_latest_external_session_id_by_workspace("
}
```
```gh-comment
{
"id": "2627707446",
"comment_type": "review",
"author": "ggordonhall",
"body": "```suggestion\r\n pub async fn cleanup_workspace(db: &DBService, workspace: &Workspace) {\r\n```",
"created_at": "2025-12-17T16:19:31Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627707446",
"path": "crates/local-deployment/src/container.rs",
"line": 146,
"diff_hunk": "@@ -142,20 +143,20 @@ impl LocalContainerService {\n map.remove(id)\n }\n \n- pub async fn cleanup_attempt_workspace(db: &DBService, attempt: &TaskAttempt) {\n- let Some(container_ref) = &attempt.container_ref else {\n+ pub async fn cleanup_workspace_container(db: &DBService, workspace: &Workspace) {"
}
```
```gh-comment
{
"id": "2627756192",
"comment_type": "review",
"author": "ggordonhall",
"body": "Update `mcp` nomenclature",
"created_at": "2025-12-17T16:31:49Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627756192",
"path": "crates/server/src/mcp/task_server.rs",
"line": 352,
"diff_hunk": "@@ -350,10 +349,9 @@ impl TaskServer {\n project_id: ctx.project.id,\n task_id: ctx.task.id,\n task_title: ctx.task.title,\n- attempt_id: ctx.task_attempt.id,\n- attempt_branch: ctx.task_attempt.branch,\n+ attempt_id: ctx.workspace.id,"
}
```
```gh-comment
{
"id": "2628161769",
"comment_type": "review",
"author": "ggordonhall",
"body": "update, and similar in other events",
"created_at": "2025-12-17T18:27:47Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2628161769",
"path": "crates/server/src/routes/task_attempts.rs",
"line": 1335,
"diff_hunk": "@@ -1295,7 +1332,7 @@ pub async fn start_dev_server(\n serde_json::json!({\n \"task_id\": task.id.to_string(),\n \"project_id\": project.id.to_string(),\n- \"attempt_id\": task_attempt.id.to_string(),\n+ \"attempt_id\": workspace.id.to_string(),"
}
```
```gh-comment
{
"id": "2628194289",
"comment_type": "review",
"author": "ggordonhall",
"body": "Ugly, but we should rename this struct to avoid confusion with the more general concept of a workspace. Ideas...\r\n\r\n- `WorktreeContainer`\r\n...\r\n...\r\n\r\nChatGPT?",
"created_at": "2025-12-17T18:36:30Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2628194289",
"path": "crates/services/src/services/workspace_manager.rs",
"line": 3,
"diff_hunk": "@@ -1,6 +1,6 @@\n use std::path::{Path, PathBuf};\n \n-use db::models::{repo::Repo, task_attempt::TaskAttempt};\n+use db::models::{repo::Repo, workspace::Workspace as DbWorkspace};"
}
```
```gh-comment
{
"id": "2628198036",
"comment_type": "review",
"author": "ggordonhall",
"body": "We could add a BE route for this, and similar hooks where we're aggregating this information on the fly",
"created_at": "2025-12-17T18:37:46Z",
"url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2628198036",
"path": "frontend/src/hooks/useTaskAttempts.ts",
"line": 43,
"diff_hunk": "@@ -16,10 +20,36 @@ export function useTaskAttempts(taskId?: string, opts?: Options) {\n const enabled = (opts?.enabled ?? true) && !!taskId;\n const refetchInterval = opts?.refetchInterval ?? 5000;\n \n- return useQuery<TaskAttempt[]>({\n+ return useQuery<Workspace[]>({\n queryKey: taskAttemptKeys.byTask(taskId),\n queryFn: () => attemptsApi.getAll(taskId!),\n enabled,\n refetchInterval,\n });\n }\n+\n+/**\n+ * Hook for components that need executor field for all attempts.\n+ * Fetches all attempts and their sessions in parallel.\n+ */\n+export function useTaskAttemptsWithSessions(taskId?: string, opts?: Options) {\n+ const enabled = (opts?.enabled ?? true) && !!taskId;\n+ const refetchInterval = opts?.refetchInterval ?? 5000;\n+\n+ return useQuery<WorkspaceWithSession[]>({\n+ queryKey: taskAttemptKeys.byTaskWithSessions(taskId),\n+ queryFn: async () => {\n+ const attempts = await attemptsApi.getAll(taskId!);\n+ // Fetch sessions for all attempts in parallel"
}
```
13 lines
377 B
JSON
13 lines
377 B
JSON
{
|
|
"db_name": "SQLite",
|
|
"query": "DELETE FROM repos\n WHERE id NOT IN (SELECT repo_id FROM project_repos)\n AND id NOT IN (SELECT repo_id FROM workspace_repos)",
|
|
"describe": {
|
|
"columns": [],
|
|
"parameters": {
|
|
"Right": 0
|
|
},
|
|
"nullable": []
|
|
},
|
|
"hash": "c27f2fd6b3696cb5a8ec54226608440786a6cec601783f797be3a8c515080d62"
|
|
}
|