DeepSeek Harness draft lost when switching a new session

TroubleshootingPublished 2026-10-20Author: DeepSeek Plugin Market
DeepSeek Harnessnew sessiondraft losslocalStoragesession switching
DeepSeek Harness loses the unsent draft when a new session gets a new id while drafts are keyed by sessionId. Three ways to recover it or stop the loss.

In DeepSeek Harness the text still exists; the index that pointed at it is gone. Drafts are stored in the browser under the session id, and creating a new session produces a new id, so the old record becomes an orphan that nothing reads. The old blank session also disappears from the sidebar under the display rules, so even the entry point vanishes (source).

Symptom and reproduction in DeepSeek Harness

Bottom line: if your sequence matches the one below, stop looking for other causes — this is fixed behaviour on the new-session path, not an intermittent fault. No DSH plugin is involved; the composer state belongs to the host UI.

  1. Click + in a workspace to create a session. Expected: an empty composer.
  2. Type a few words and do not send. Expected: the text stays in the input.
  3. Click any existing session in the sidebar. Expected: a normal switch.
  4. Click + again. Expected: an empty input, and the blank session from step 1 is no longer anywhere in the sidebar.
  5. Prove the text was not lost: go back to the existing session from step 3 and its own unsent draft still restores. Expected: only the new-session path loses content.

A session that looks empty can also come from the render side; see blank session and hidden sidebar.

Why it happens: a documented 0.2.1 change

Bottom line: since 0.2.1, a no-argument new session deliberately creates a fresh session and agent so a stale preset from an old empty session cannot leak in; the reuse code was not deleted, it is simply no longer the default.

Community review in the discussion found that startSession() with no options passes undefined all the way down and ends at sessions.create({ workspaceId }). A call with an explicit preset still takes the reuse branch, finds the blank session in the same cwd and hands its sessionId to the host (per the discussion, community source review). That matters because the one-line "change undefined to {} and reuse comes back" patch restores exactly the behaviour this change retired. What is actually missing is a migration of the old draft when the id changes, and an entry point for the old blank session.

Where the draft lives: one record, three fields

Bottom line: the draft is not in the session file; it is in browser localStorage under a key that contains the sessionId. The few hundred bytes in the session log have nothing to do with it.

  1. Open DevTools and go to Application → Local Storage. Expected: a group of keys prefixed dsh.conversation..
  2. Read the values one by one; the shape is { draft, view, viewRequest } and draft.text holds your words. Expected: you can locate the record containing the target text.
  3. Note the sessionId suffix of that key. Expected: it matches the blank session that no longer appears in the sidebar.
  4. Read dsh.sessions.current, which points at the current session id. Expected: it differs from the id in step 3 — the key survived, the pointer moved.

Fix 1: add a handOffDraft patch to DeepSeek Harness

Bottom line: this follows the upstream direction — keep creating a new session, but before the selection is published, hand the discarded blank draft to the new composer, newest first.

  1. Find the point in ui-workspace where the new session is retained but its selection has not been published. Expected: both session ids are still reachable there.
  2. List the workspace's unstarted blank sessions as candidates, ordered by most recent use. Expected: only sessions that have never sent a message.
  3. Read each candidate's draft: the live snapshot if still mounted, the last saved draft if not. Expected: no empty reads.
  4. Skip candidates with no text, no reference chips and no command markers. Expected: empty drafts are not moved.
  5. Import the first non-empty draft into the target session through the existing structured draft editor, write the target sessionId record, then delete the source record. Expected: the text appears in the new composer and the old key is gone.
  6. Cover the reverse guards: target already has text or attachments, target is submitting, navigation did not retain the target — in all three, touch neither side. Expected: a user's fresh input is never overwritten.
  7. Reload and retest. Expected: the draft is still there, which proves you wrote the persistent record and not just component state.

Fix 2: rescue the DeepSeek Harness draft through localStorage

Bottom line: for a draft already lost, with no wish to touch build output, point the current-session pointer back at the old blank session — the composer will read the draft by id after a reload.

  1. In the DevTools console, list the draft keys and find the one whose draft.text holds your text; copy its sessionId. Expected: a session id string.
  2. Run the two lines below, replacing <old-sessionId> with that value:
js
localStorage.setItem('dsh.sessions.current', JSON.stringify({ sessionId: '<old-sessionId>' }))
location.reload()
  1. After the reload, confirm the composer restores the text and the reference chips. Expected: the sidebar shows that blank session as current again.
  2. Send the content or copy it elsewhere immediately. Expected: you no longer depend on this fragile pointer.
  3. Order matters: reload right after setting the key, because any in-app navigation in between rewrites dsh.sessions.current. Expected: a shorter gap means a higher success rate.
  4. If the desktop build hides DevTools, use F12, Ctrl+Shift+I on Windows or ⌘⌥I on macOS; the packaged build accepts them too.
  5. Keep a copy of the dsh.conversation.* record. Expected: you will not have to hunt for the sessionId next time.
  6. If the stored draft is a plain string rather than an object, the record comes from an older version. Expected: upgrade before reading it, rather than forcing an object parse.

Fix 3: restore blank-session reuse in DeepSeek Harness

Bottom line: if your team cares more about never losing a draft than about a clean preset on every +, send the no-argument path through the reuse branch again.

  1. Accept the trade-off first: reuse inherits the old empty session's preset definition, which is why upstream changed it. Expected: you know what you are giving up.
  2. Pass a reuse preset (an empty object rather than undefined) on the no-argument branch so connectBlank enters the cwd-based reuse path. Expected: + stops creating a new id.
  3. Re-run the four-step reproduction. Expected: the draft stays in place and no hidden session is added to the sidebar.
  4. Check disk: each + used to leave an empty session log around 350 bytes; after the change no new ones appear. Expected: you can clean the old directories by hand.
  5. Record the divergence from upstream and re-evaluate it on each upgrade. Expected: if upstream changes reuse semantics, you notice instead of silently breaking.
  6. Test the newline boundary: whitespace-only input in a blank session still counts as content. Expected: it is not treated as an empty draft and dropped.
  7. Test multiple workspaces: a blank left in workspace A must not be reused by + in workspace B. Expected: reuse stays inside the same cwd.

Choosing a path

Bottom line: the three paths solve different problems, and only one of them rescues data that is already gone.

PathWhat it solvesWhat it does not
handOffDraft patchNew sessions still get new ids, drafts follow automaticallyAttachments do not move; one-shot empty drafts are not transferred
Manual localStorage rescueRecovers drafts already lostRequires manual work; attachments are still missing
Restore reuseThe id never changes in the first placeContradicts the 0.2.1 direction and is easy to break on upgrade

If you are still losing drafts, use path two to save the data first, then decide between paths one and three for the long term.

Limits of DeepSeek Harness draft storage

Bottom line: drafts live in one browser's local storage, so every recovery path stops at the boundary of that origin and that profile.

  1. Drafts exist only in the current browser. Switching browsers or clearing site data removes them permanently.
  2. Attachments and images are runtime objects that never entered the draft record, so no path can restore them.
  3. Copy a value out to a text file before editing localStorage, so a bad write is reversible.
  4. The patch does not change the persistence format, so old records stay readable. If you see a shape other than { draft, view, viewRequest }, check the version first.
  5. The mechanics and code locations above come from community source review in the discussion; upstream has not fixed it, so treat this as temporary.
  6. Clearing site data, changing browsers or changing ports (localhost and 127.0.0.1 are different origins) all make the draft keys unreadable — confirm the address did not change before debugging.

DeepSeek Harness FAQ

Bottom line: the remaining questions are about scope, and they decide how much of the draft you can actually get back.

Q: Does clicking another session in the same workspace always destroy the draft? A: No. An existing session keeps its id, so its draft is read as usual. Only the unsent draft of a session whose id is abandoned is affected.

Q: Are reference chips recoverable too? A: Yes through both the patch and the localStorage pointer, because they live in the same record. Attachments do not.

Q: Is this safe after a version upgrade? A: The localStorage keys are stable enough for a rescue, but the patch targets client code that changes between releases. Re-check the insertion point after every upgrade.

FAQ

I typed in a new session, opened another chat, then clicked + again. Is the draft gone for good?

No — DeepSeek Harness never deletes the text; it is stored per sessionId in a localStorage record under dsh.conversation.*. The new + created a different id, so nothing reads the old key any more and the UI shows an empty composer.

Why is this called a documented change instead of a regression?

DeepSeek Harness 0.2.1 shipped an upgrade guide stating that New Session creates a brand-new session and agent and no longer reuses an empty workspace session. The reuse path still exists; the no-argument call just no longer takes it, which turns this into an unmigrated-draft problem.

Can I recover the text through localStorage without installing a patch?

Yes — DeepSeek Harness keeps the text in dsh.conversation.*; in DevTools, find the record whose draft.text holds your text, write its sessionId into dsh.sessions.current, then reload immediately. Text, reference chips and command chips return; attachments and images do not, because they never entered draft storage.

Does the handOffDraft patch also move attachments from the old blank session?

No — the DeepSeek Harness handOffDraft patch moves text, reference chips and command chips only. Attachments are browser objects belonging to the original session and stay there. The patch also leaves the old session in place and transfers only the draft record.

What does going back to blank reuse cost, apart from drafts?

Going back to blank reuse makes DeepSeek Harness inherit the old empty session's preset definition, which is exactly what 0.2.1 set out to avoid. You keep the id and the draft stays in place, but the divergence from upstream must be maintained and is easy to break on upgrade.

Related Terms

blank session
An empty session that has never sent a message. The sidebar shows it only while it is the current session, and its id is the key under which the draft is stored, so a new id is a lock that no longer opens the old content.— https://github.com/deepseek-ai/deepseek-harness/discussions/10021
dsh.conversation.*
The localStorage key prefix for composer drafts, one record per sessionId holding the text, references and command markers. If the session id in the key no longer exists, no view reads that draft again.— https://github.com/deepseek-ai/deepseek-harness/discussions/10021
startSession()
The client entry point for creating a session. With a preset argument it takes the reuse branch and can adopt an existing empty session in the workspace; called with no arguments it creates a new session, so every + press yields a new id.— https://github.com/deepseek-ai/deepseek-harness/discussions/10021

Sources