WorkBuddy Workspace Migration & Data Recovery
Purpose
WorkBuddy stores session data across multiple local files. Renaming or moving a workspace directory breaks the path-based linkage between sessions and their workspace, causing sessions to disappear from the UI. This skill documents the complete storage architecture and the steps to recover sessions.
When to Use
- Sessions disappeared after renaming or moving a workspace directory
- Need to merge sessions from multiple workspaces into one
- Want to understand how WorkBuddy stores session data locally
- Need to recover "deleted" (archived) sessions
- Need to physically purge soft-deleted sessions or stale workspaces to free disk space
- Want to pick specific sessions out of a "temp dump" workspace and group them into dedicated workspaces (post-hoc clustering)
Data Storage Architecture
WorkBuddy stores data in ~/.workbuddy/ across these layers:
1. Session Content: projects/{slug}/*.jsonl
Each workspace gets a slug directory under ~/.workbuddy/projects/. Inside, each session is a
JSON Lines file named by conversationId. Each line is a JSON object representing one message
(user message, AI reply, tool call, etc.).
Slug naming: path D:\work\临时 becomes d-work-临时 (lowercase, :\ replaced with -).
The JSONL records contain a cwd field per line — if this doesn't match the workspace path,
the UI may not display the session.
2. Session Metadata & Workspaces: workbuddy.db (SQLite 3.x)
sessions table:
id— conversation UUIDcwd— workspace pathtitle— session titlestatus— session statusdeleted_at— controls visibility:IS NULL= visible,IS NOT NULL= hidden (archived)is_playground—1= auto-created (never saved to workspace),0= explicitly saved to workspace. Playground sessions are filtered out of workspace task lists in the UI.user_id,mode,permission_mode,project_id, etc.
workspaces table:
path— workspace directory pathlast_opened— timestamp (ms)
The UI enumerates workspaces from this table. If a workspace is not registered here, sessions bound to it will not appear even if all other data is correct. Migrating a workspace must add the new path and remove the old one.
3. Session Mapping: app/sessions.json
Lightweight JSON cache mapping conversationId to workDir.
4. Other Storage
file-history/{conversationId}/— versioned file snapshots per sessionartifact-index/{conversationId}.json— artifact summariesblobs/— uploaded images/filesapp/session/IndexedDB/— Electron IndexedDB (LevelDB), may contain session state
Visibility Control
The definitive flag for whether a session appears in the UI is workbuddy.db.sessions.deleted_at:
deleted_at IS NULL → visible in task list
deleted_at IS NOT NULL → hidden (archived/deleted)
Archiving a session through the UI sets deleted_at to the current timestamp. It does NOT delete
the JSONL file or any other data — it's a soft delete. To physically reclaim disk space, use the
purge script (see "Session Purge" section below).
One-Click Migration (Recommended)
When the user says "migrate workspace from X to Y", run the bundled script:
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/migrate.py "<old_dir>" "<new_dir>"
The script handles all steps automatically:
- Copies workspace files from old to new directory (uses
copytreewithdirs_exist_ok=True— safe while WorkBuddy is running, won't overwrite existing files that differ) - Moves JSONL files from old project slug to new slug (avoids duplicate entries)
- Updates
cwdin every JSONL record (case-insensitive matching) - Updates
workbuddy.db:sessions.cwd→ new pathsessions.deleted_at→ NULL (un-archive)sessions.is_playground→ 0 (convert auto-created sessions to normal)
- Updates
workspacestable: registers new workspace (useslast_opened_atcolumn), removes old one - Updates
app/sessions.jsonif entries exist - Cleans up empty old slug directory (if all JSONL files moved successfully)
Bug fixes (2026-09-02):
- Fixed
workspacestable column name:last_opened→last_opened_at(matches actual DB schema) - Added empty slug directory cleanup after JSONL move to prevent orphan directories
If the old directory no longer exists (already moved manually), use --no-copy:
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/migrate.py "<old_dir>" "<new_dir>" --no-copy
Preview changes without applying:
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/migrate.py "<old_dir>" "<new_dir>" --dry-run
After running: tell the user to restart WorkBuddy for sessions to appear.
Multi-user: By default, both scripts only operate on the current user's sessions (auto-detected
via sessions.json → userId). Use --no-user-filter to bypass, or --user-id to specify a different user.
Session & Workspace Purge (Physical Delete)
⚠️ CRITICAL —
--no-clean-orphans铁律(已验证)WorkBuddy 的"删除"本质是软删除:仅置
deleted_at隐藏会话,JSONL / file-history / artifact 仍占磁盘。 自加固版本起:会话级与工作区级清理默认即带--no-clean-orphans,脚本不再自动运行孤儿清理,默认绝不触碰任何工作区目录。仅当显式运行--clean-orphans(孤儿清理模式)时,脚本才可能rmtree工作区目录;届时强制先列出待删工作区绝对路径并经--yes/交互确认授权后方可执行(见下方 🔐 授权协议)。 后果实证(加固前旧行为):一次会话级清理遗漏该参数,导致 7 个工作区目录(含用户自有项目文件)被整棵移入D:\$Recycle.Bin。 铁律:凡是只想清理会话、保留工作区目录的会话级清理,命令必须带--no-clean-orphans(现已成为默认,脚本完全不触碰工作区目录,无需额外授权)。任何会删除工作区目录的操作(孤儿清理、--purge-workspace)都必须先列出待删工作区绝对路径、并经用户明确授权后方可执行,绝不依赖自动孤儿清理。
WorkBuddy's "delete" is a soft delete — it only sets deleted_at to hide the session.
All JSONL files, file history, and artifact data remain on disk, consuming space.
Similarly, workspaces with no active sessions remain registered in the database
and may have stale directories and session data still on disk.
Use the bundled purge.py script for three levels of cleanup:
Session-Level Purge
Delete individual soft-deleted sessions. 自加固版本起,会话级清理默认即等价于携带 --no-clean-orphans:清理后不自动运行孤儿清理,绝不触碰任何工作区目录——仅删除 ~/.workbuddy/ 下的 JSONL / file-history / artifact / DB 行 / sessions.json 条目(见 physically_delete_session)。历史教训:加固前版本默认会跑孤儿清理,曾因遗漏该参数导致 7 个工作区目录被整棵误删。故显式携带 --no-clean-orphans 仍是好习惯(跨版本保险)。仅显式 --clean-orphans 才会触发孤儿清理,且届时强制列出待删工作区绝对路径并经授权(见下方 🔐 授权协议)。
List all soft-deleted sessions (default, safe):
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py
Purge ALL soft-deleted sessions:
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py --all
Purge by filters:
# By ID prefix
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py --ids 794f328e 0b2cc7e2
# Older than N days
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py --older-than 30
# From a specific workspace
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py --workspace "D:\\work\\temp"
# Larger than N KB
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py --min-size 100
# Combine + dry run
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py --older-than 30 --dry-run
Workspace-Level Purge
Delete an entire workspace — all its sessions, disk files, and DB records.
List all workspaces with active/inactive status (safe, first step):
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py --list-workspaces
Outputs a table showing: last opened date, active/total/deleted session counts, disk usage.
Workspaces with 0 active sessions are marked with * — these are cleanup candidates.
Purge a specific inactive workspace:
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py --purge-workspace "c:\\Users\\User\\WorkBuddy\\Claw"
This permanently deletes:
- All sessions belonging to that workspace (even soft-deleted ones)
- The
projects/{slug}/directory (all JSONL files) - The workspace directory on disk
- The workspace entry from
workspacestable - All session entries from
sessionstable - All matching entries from
sessions.json - All
file-history/andartifact-index/for those sessions
Purge ALL inactive workspaces at once (DANGER — confirm before running):
# Preview first
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py --purge-workspace --all-inactive --dry-run
# Execute
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py --purge-workspace --all-inactive
Orphan Directory Cleanup
⚠️ 危险提示:
--clean-orphans不仅会删空projects/{slug}/目录和过期 DB 条目,还会shutil.rmtree任何"磁盘存在且 0 会话"的工作区目录本身(含其中的用户项目文件)。绝不把它当作"无害清理"。会话级清理请改用--no-clean-orphans跳过它。
🔐 授权协议(必须):孤儿目录清理与自动孤儿清理是脚本中唯一会
rmtree工作区目录的代码路径。任何会触及工作区目录/文件的操作,都必须先列出将被删除的工作区目录绝对路径(如D:\Documents\AI_Work_Temp\XXX),且只有得到用户明确授权后才能执行。标准流程:--clean-orphans --dry-run先列出路径 → 用户核对无误并显式确认(或加--yes/-y)→ 再实际执行。会话级清理(带--no-clean-orphans)完全不触碰工作区目录,故无需此授权。
After session purging or workspace migration, empty directories and stale DB entries
may remain on disk. These are NOT reachable through workspace-level purge (they're not
in the workspaces table or have no sessions).
Preview orphans (safe):
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py --clean-orphans --dry-run
Clean all orphans:
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/purge.py --clean-orphans
What gets cleaned:
- Empty
projects/{slug}/directories — slug dirs with no remaining.jsonlfiles (and any leftover non-JSONL files) - Workspace directories with 0 sessions for ANY user — workspace dirs that no longer have sessions (regardless of user_id)
- Stale
workspacestable entries — records pointing to directories that no longer exist on disk and have 0 sessions
Safety: workspace directories are only deleted if NO user (any user_id) has sessions referencing them. This prevents accidentally deleting other accounts' workspace data.
What Gets Deleted
| Mode | Scope | Includes |
|---|---|---|
| Session purge | One session | JSONL, file-history, artifact-index, DB row, sessions.json entry |
| Workspace purge | Entire workspace | All sessions above + workspace directory + projects/ slug dir + workspaces table |
Note: blobs/ files are NOT deleted (they may be shared across sessions).
Multi-User Safety (Critical)
Multiple WorkBuddy accounts on the same machine share the same ~/.workbuddy/ directory.
Without user filtering, purge/migrate would operate on ALL users' sessions — potentially
deleting or breaking data belonging to other accounts.
Both scripts use sessions.user_id filtering by default. The current user's ID is
auto-detected from sessions.json (most recent entry's userId) with fallback to the
most recently active session in workbuddy.db.
# Default: auto-detect current user (safe)
uv run --project D:/Tools/Assembly/python/myenv python purge.py --all
# Manual: specify user_id
uv run --project D:/Tools/Assembly/python/myenv python purge.py --all --user-id 28bfa73c-3367-4b19-a753-ccae8ac3a4ef
# Dangerous: operate on ALL users (use with caution!)
uv run --project D:/Tools/Assembly/python/myenv python purge.py --all --no-user-filter
The same --user-id and --no-user-filter options apply to migrate.py as well.
After Purge
The script automatically runs VACUUM on workbuddy.db to reclaim space.
自加固版本起,孤儿清理默认不自动运行(会话级/工作区级清理等价于默认 --no-clean-orphans)。只有显式 --clean-orphans 才会执行孤儿清理;届时 clean_orphans 会先列出每个待删工作区目录的绝对路径,并经 --yes/交互确认授权后,才 shutil.rmtree 该目录(包含其中的用户项目文件)。因此:
- 只想清会话、保留工作区目录 → 永远带
--no-clean-orphans。 - 确实要连目录一起删 → 用
--purge-workspace(语义明确、可控),不要依赖自动孤儿清理。
Restart WorkBuddy to ensure the UI reflects the changes.
Output Formats
- Default: human-readable table with size totals
--json: machine-readable JSON array (for programmatic use)--dry-run: preview mode, no files touched
Verified Operational Playbook (最平凡流程 · 已验证)
基于一次完整会话(盘点 → 会话级清理 → 误删 → 回收站恢复 → 查询 → 方案沉淀)实证总结。核心目标:只清会话数据,绝不触碰工作区目录中的用户文件。
本机工作区根目录(权威)
D:\Documents\AI_Work_Temp—— 默认工作空间存储路径(绝大多数工作区在此)D:\Documents\AI_MCP-Skill-CLI—— 第二个工作区根(如AI_MCP-Skill-CLI\ref-material-writing、AI_MCP-Skill-CLI\we-mp-rss等子目录)
会话数据物理分离于两处:
| 位置 | 内容 | 处置 |
|---|---|---|
~/.workbuddy/projects/{slug}/*.jsonl |
会话内容 | 会话级清理删除 |
~/.workbuddy/workbuddy.db(sessions 表 cwd 匹配) |
会话元数据 | 会话级清理删除 |
~/.workbuddy/app/sessions.json |
缓存映射 | 会话级清理删除 |
<工作区>/.workbuddy/ |
该工作区 WorkBuddy 元数据(memory/artifact) | 显式删除 |
<工作区>/(根目录其他文件) |
用户项目文件 | 一律不动 |
"最平凡流程"5 步(会话级清理标准动作)
- 预览:
purge.py --workspace "<路径>" --no-clean-orphans --dry-run,确认输出只含~/.workbuddy条目、无任何D:\Documents\AI_Work_Temp目录操作。 - 执行清理:去掉
--dry-run运行同上命令,物理删除 JSONL / file-history / artifact / DB 行 / sessions.json 条目。 - 删工作区内 WorkBuddy 元数据:额外
rm -rf "<工作区>/.workbuddy"(确认该目录确为 WorkBuddy 元数据、不含用户文件;本机需沙箱外执行,见下)。 - 校验:工作区目录
D:\Documents\AI_Work_Temp\XXX完好、用户文件无缺;~/.workbuddy/projects/{slug}该会话条目已清零。 - 收尾:重启 WorkBuddy 使 UI 同步。
本机执行约束(已验证)
- Python 调用:一律
uv run --project D:/Tools/Assembly/python/myenv python <脚本>,禁用裸python/pip(python3亦不可用)。 - Git Bash 路径:脚本路径与工作区路径写 Windows 原生盘符(
C:/Users/15794/...或C:\Users\...),勿用/c/Users/...——否则 uv 拼成相对路径导致No such file or directory。 - safe-delete 沙箱:WorkBuddy 注入
sitecustomize.py拦截删除 API,沙箱内删除被拒/改移回收站。真实删除需dangerouslyDisableSandbox: true并unset CODEBUDDY_SAFE_DELETE_*。第 3 步删.workbuddy目录建议沙箱外执行。 - BULK_GUARD:单回合累计删文件 > 50 触发
SystemExit(1)中断。批量清理应分批或沙箱外 + unset 安全删除变量。 - 多卷回收站:
C:\$Recycle.Bin存 C 盘数据,D:\$Recycle.Bin存 D 盘数据,恢复误删时按盘分别处理。
复用口诀
会话清理两处删(
~/.workbuddy+ 工作区.workbuddy),工作区目录永不动;--no-clean-orphans已为默认(显式携带更保险),先--dry-run再动手。
Recycle Bin Recovery (误删工作区目录的恢复 · 已验证)
若遗漏 --no-clean-orphans 已把工作区目录整棵移入回收站,按此恢复(本次已验证:38 目录成功还原 / 0 失败):
- 定位:遍历
D:\$Recycle.Bin\<SID>\下所有$Ixxxx文件,解析原始路径。 - 解析
$I(关键,勿错):
切勿用def parse_i(ipath): data = open(ipath, 'rb').read() idx = data.find(b'D\x00:\x00\\\x00') # 直接搜 UTF-16-LE 的 "D:\" if idx == -1: return "" return data[idx:].decode('utf-16-le', errors='ignore').rstrip('\x00').rstrip(' ')data.find(b'\x00\x00')找终止符——会错误匹配"末 ASCII 字符低字节 + 终止符首字节"组成的伪00 00,吞掉路径最后一个字符(如Claw→Cla)。 - 筛选:仅恢复
D:\Documents\AI_Work_Temp\<单段>\与D:\Documents\AI_MCP-Skill-CLI\(及其下一级)中$Rxxxx为目录的项;排除散落文件(.md/.txt/.json/.log)、Deepseek-pp内部子文件、系统临时(D:\System\UserTemp\*)。 - 还原:
os.rename($Rxxxx, 原始路径),父目录优先(避免子项重复),目标已存在则跳过。 - 校验:恢复后
os.path.exists(原始路径)验证;交叉核对workbuddy.db。注意非常规位置(如GitExtensions-Git图形界面实际在D:\Tools\,不在AI_Work_Temp,始终完好)。
经验:今后任何
purge.py会话级清理务必加--no-clean-orphans;误删进回收站可还原,但真实物理删除(绕过沙箱)不可逆,须二次确认。
Session Organization (Post-Hoc Clustering)
A common real-world workflow: you dump everything into a single "temp" workspace as you go, and only later realize that some sessions are related and deserve their own dedicated workspace.
Use organize.py to pick specific sessions out of a source workspace and move them to a
target workspace (new or existing), without touching the rest.
List sessions in a workspace:
uv run --project D:/Tools/Assembly/python/myenv python ~/.workbuddy/skills/workbuddy-workspace-migration/scripts/organize.py "D:\\work\\temp"
Output:
# Updated Size Title
[0] 06-25 17:14 30KB 是不是太能弄
[1] 06-25 05:26 874KB 中金对这个AI Agent在企业应用中的发展很看好,但通过主要是要加从场景侧
[2] 06-25 03:16 3MB n8n 和dify,对比
...
To pick sessions:
organize.py "D:\work\temp" --pick 0,2,5 --to "D:\work\new" --dry-run
Pick by index and move:
# Preview first
uv run --project D:/Tools/Assembly/python/myenv python organize.py "D:\\work\\temp" --pick 0,2,5 --to "D:\\work\\infra" --dry-run
# Execute
uv run --project D:/Tools/Assembly/python/myenv python organize.py "D:\\work\\temp" --pick 0,2,5 --to "D:\\work\\infra"
Pick by ID prefix (useful when you have IDs from somewhere else):
uv run --project D:/Tools/Assembly/python/myenv python organize.py "D:\\work\\temp" --pick-ids 794f328e 0b2cc7e2 --to "D:\\work\\infra"
What happens under the hood (same four-layer handling as migrate.py, but per session):
- JSONL file moved from
projects/{src_slug}/toprojects/{dst_slug}/ cwdfield inside JSONL records updated (case-insensitive match)workbuddy.dbsessions row:cwdupdated,deleted_atcleared,is_playgroundset to 0app/sessions.jsonentry updated- Target workspace auto-registered in
workspacestable if missing file-history/andartifact-index/left in place (keyed by session ID, no path ref)
After moving, if the source workspace is now empty and you want to clean it up:
- 若源工作区仍有用户项目文件,不要运行
--clean-orphans(它会shutil.rmtree整个工作区目录)。只手动删除~/.workbuddy/projects/{src_slug}/即可。 - 仅当源是纯临时工作区、无任何你要在意的用户文件时,才运行
purge.py --clean-orphans清空 slug 目录与过期 DB 条目。
Manual Migration Procedure (Fallback)
If the script cannot be used, follow these steps manually:
Step 1: Identify sessions
Query workbuddy.db:
SELECT id, cwd, title, deleted_at FROM sessions WHERE cwd LIKE '%old%';
Step 2: Determine slugs
Path D:\work\临时 → slug d-work-临时 (lowercase, :\→-).
Step 3: Copy JSONL files
Copy ~/.workbuddy/projects/{old-slug}/*.jsonl to ~/.workbuddy/projects/{new-slug}/.
Step 4: Update cwd in JSONL
Each line has a cwd field. CRITICAL: JSONL may have different casing than workbuddy.db
(e.g. c:\Users\... vs C:\Users\...). Match case-insensitively.
Step 5: Update workbuddy.db
UPDATE sessions SET cwd = 'D:\\new\\path' WHERE cwd LIKE 'D:\\old\\path';
UPDATE sessions SET deleted_at = NULL WHERE cwd = 'D:\\new\\path' AND deleted_at IS NOT NULL;
Step 6: Update sessions.json
Update workDir field for matching conversationId entries.
Step 7: Restart WorkBuddy
Diagnostic Queries
List all sessions for current workspace:
SELECT id, cwd, title, deleted_at FROM sessions WHERE cwd = 'D:\\work\\临时';
Count sessions per workspace:
SELECT cwd, COUNT(*) FROM sessions GROUP BY cwd;
List registered workspaces:
SELECT path, last_opened FROM workspaces;
Find playground (auto-created) sessions:
SELECT id, cwd, title FROM sessions WHERE is_playground = 1;
Find which JSONL files exist vs. which sessions are in the database:
ls ~/.workbuddy/projects/d-work-临时/
uv run --project D:/Tools/Assembly/python/myenv python -c "import sqlite3; c=sqlite3.connect('~/.workbuddy/workbuddy.db').cursor(); c.execute(\"SELECT id FROM sessions WHERE cwd='D:\\\\work\\\\临时'\"); print([row[0] for row in c.fetchall()])"
Important Notes
workbuddy.dbis SQLite 3.x, confirmed byfilecommand and hex headerSQLite format 3\0- Chinese characters in paths are stored natively (UTF-8), both in SQLite and JSONL
- Do NOT delete
workbuddy.dborsessions.json— they are the primary data sources - Most session content is local (JSONL), not server-stored
- Two accounts on the same machine share the same
~/.workbuddy/projects/directory structure - Auto-created workspaces (no explicit "Save to Workspace") have
is_playground=1and are NOT registered in theworkspacestable — migration must fix both - The
workspacestable is the authoritative source for UI workspace enumeration; a missing entry means the workspace won't appear in the sidebar