You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After upgrading to 0.1.5-rc.1, all v0 sessions fail to load in the Web UI with:
failed to observe session "session-...":
@deepseek-ai/dsh-session-format-v0-to-v1 refuses this format v0 Session:
permission/preset 0 data has unexpected member "origin";
source v0 artifact remains unchanged (raw log: .../session.jsonl.zstd)
The migrator refuses and leaves the artifact unchanged, so no data is lost — but the session history is unloadable until the issue is worked around.
Root cause
Legacy dsh (≤0.1.1) actually writes the origin member into permission/preset events:
But dsh-session-format-v0-to-v1 declares the allowed member list for this event as ["preset"] only:
"permission/preset": disposition(["preset"]),
Any v0 session containing this field is rejected outright. Long-running sessions can also contain the same event mid-stream (observed at seq 615837 and 1065398), so this is not limited to session headers.
Reproduction
Have v0 sessions written by dsh ≤0.1.1 whose permission/preset events carry data.origin (this is what the default preset application path writes).
Upgrade to 0.1.5-rc.1.
Open the session in the Web UI → "failed to observe session".
Scope observed locally
16 of my v0 sessions carry this field and all fail to load; every session without the field loads fine.
Workaround (verified locally)
Strip data.origin from permission/preset events in the v0 jsonl logs.
Important: session files are multi-frame zstd, and assertZstdHeaderFrame requires the first frame to contain exactly the header line. Rewriting therefore needs frame-aware recompression — the header line as frame 1, the remaining lines as frame 2. A naive whole-file recompress produces corrupt Zstandard session log: first frame is not exactly one header line at boot.
The following script does this safely (backup first, per-file validation, verified against a real session):
#!/bin/bash# fix-dsh-v0-sessions-v3.sh — 修复 DSH 0.1.5 迁移器拒绝的旧会话(修订版)## 相对用户版本修复 3 处:# 1. splitlines() → split('\n') # 防 U+2028/U+2029 拆坏 JSONL# 2. os.replace 前补 chmod 600 # 保持原文件权限# 3. 增强验证:帧结构 + 行数一致 + origin 已清除## 帧结构:header 行单独一帧,body 一帧(DSH 要求第一帧恰好一行)set -e
SESSIONS_ROOT="$HOME/.dsh/sessions"
TS=$(date +%Y%m%d-%H%M%S)
ZSTD=$(command -v zstd ||echo /opt/homebrew/bin/zstd)
MODE="${1:-single}"# single = 只修第一个(先测试);all = 全部;only <id> = 只修指定会话
ONLY_ID="${2:-}"echo"=== DSH v0 会话修复 v3(分帧 + 安全修订)==="echo"模式: $MODE$ONLY_ID"echo""
fixed=0
skipped=0
failed=0
forfin"$SESSIONS_ROOT"/*/*/session.jsonl.zstd;do
[ -f"$f" ] ||continue# only 模式:只处理指定会话 IDif [ "$MODE"="only" ] && [ -n"$ONLY_ID" ];thencase"$f"in*"$ONLY_ID"*) ;;
*) continue ;;
esacfiif!"$ZSTD" -dc "$f"2>/dev/null | grep '"permission/preset"'| grep -q '"origin"';then
skipped=$((skipped+1))continuefiecho"修复: $f"
cp "$f""$f.bak-$TS"if python3 - "$ZSTD""$f"<<'PYEOF'import subprocess, json, sys, oszstd, src = sys.argv[1], sys.argv[2]tmp = src + '.tmp'raw = subprocess.check_output([zstd, '-dc', src])lines = raw.decode('utf-8').split('\n') # 修复1:不用 splitlines,只按 \n 分if not lines or not lines[0]: sys.exit('header missing')header = lines[0]old_line_count = len([l for l in lines if l])body_lines = []for line in lines[1:]: if not line: continue try: obj = json.loads(line) except json.JSONDecodeError: body_lines.append(line) # 撕裂尾部原样保留 continue if obj.get('type') == 'permission/preset': data = obj.get('data') if isinstance(data, dict) and 'origin' in data: del data['origin'] body_lines.append(json.dumps(obj, separators=(',',':'), ensure_ascii=False))body = ('\n'.join(body_lines) + '\n') if body_lines else ''# 分帧写入:header 一帧 + body 一帧with open(tmp, 'wb') as out: out.write(subprocess.check_output([zstd, '-q'], input=(header + '\n').encode())) if body: out.write(subprocess.check_output([zstd, '-q'], input=body.encode()))# 修复2:保持原文件权限 600os.chmod(tmp, 0o600)# 验证1:zstd 完整性subprocess.check_call([zstd, '-t', tmp])# 验证2:解压后行数一致new_raw = subprocess.check_output([zstd, '-dc', tmp])new_line_count = len([l for l in new_raw.decode('utf-8').split('\n') if l])if new_line_count != old_line_count: sys.exit(f'line count mismatch: {old_line_count} -> {new_line_count}')# 验证3:第一帧恰好一行(DSH assertZstdHeaderFrame 的语义)first_frame_end = new_raw.find(0xFD2FB528.to_bytes(4, 'little'), 4)if first_frame_end == -1: # 单帧文件(只有 header):第一帧 = 全文,header 必须恰好一行 passframe1 = new_raw[:first_frame_end] if first_frame_end != -1 else new_raw# 从压缩帧无法直接看内容,改为验证解压全文第一行是 session headerfirst_line = new_raw.decode('utf-8').split('\n')[0]obj = json.loads(first_line)if obj.get('type') != 'session': sys.exit(f'first line is not session header: {obj.get("type")}')# 验证4:origin 已清除if b'"origin"' in new_raw.split(b'"permission/preset"')[1].split(b'\n')[0] if b'"permission/preset"' in new_raw else False: sys.exit('origin still present')os.replace(tmp, src)PYEOFthenecho" ✅ 完成(备份: $f.bak-$TS)"
fixed=$((fixed+1))else
rm -f "$f.tmp"echo" ❌ 失败,原文件未动"
failed=$((failed+1))fiif [ "$MODE"="single" ] || [ "$MODE"="only" ];thenecho""echo"=== 单会话模式完成 ==="echo"验证 DSH 能启动并加载该会话:"echo" dsh web"echo"确认没问题后再批量跑:"echo" bash $0 all"exit 0
fidoneecho""echo"=== 完成 ==="echo"修复: $fixed | 跳过: $skipped | 失败: $failed"echo""echo"重启 DSH 验证:"echo" kill \$(lsof -ti :3080) && dsh web"
Suggested fix
Add origin as an allowed optional member for permission/preset:
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Environment
Symptom
After upgrading to 0.1.5-rc.1, all v0 sessions fail to load in the Web UI with:
The migrator refuses and leaves the artifact unchanged, so no data is lost — but the session history is unloadable until the issue is worked around.
Root cause
Legacy dsh (≤0.1.1) actually writes the
originmember intopermission/presetevents:{"type":"permission/preset","seq":0,"time":...,"data":{"preset":"workspace-write","origin":"default"}}But
dsh-session-format-v0-to-v1declares the allowed member list for this event as["preset"]only:Any v0 session containing this field is rejected outright. Long-running sessions can also contain the same event mid-stream (observed at seq 615837 and 1065398), so this is not limited to session headers.
Reproduction
permission/presetevents carrydata.origin(this is what the default preset application path writes).Scope observed locally
16 of my v0 sessions carry this field and all fail to load; every session without the field loads fine.
Workaround (verified locally)
Strip
data.originfrompermission/presetevents in the v0 jsonl logs.Important: session files are multi-frame zstd, and
assertZstdHeaderFramerequires the first frame to contain exactly the header line. Rewriting therefore needs frame-aware recompression — the header line as frame 1, the remaining lines as frame 2. A naive whole-file recompress producescorrupt Zstandard session log: first frame is not exactly one header lineat boot.The following script does this safely (backup first, per-file validation, verified against a real session):
Suggested fix
Add
originas an allowed optional member forpermission/preset:Alternatively, map/drop
originduring v0→v1 migration if it has no v1 equivalent.All reactions