DeepSeek Harness bash hangs 300s on !: history expansion

TroubleshootingPublished 2026-10-30Author: DeepSeek Plugin Market
DeepSeek HarnessDSHbashhistory expansiontimeoutescapingcommand line
A bash command with ! in DeepSeek Harness spins for 300 seconds and returns only dsh> prompts; history expansion eats the line. Reproduction and workaround.

One printf 'p4-start %s p4-end\n' '!' runs a full 300 seconds in the bash tool and returns not a result but a screen of dsh> prompts. The command itself is flawless and returns instantly in a terminal; the hang comes from it being wrapped in $'...' when delivered, while the interactive bash performs history expansion on ! at read-line time and discards the whole line (#10636). What follows reproduces and verifies it in experiment order.

DeepSeek Harness: Experiment 1: minimal reproduction, change one character

The only controlled variable is whether the command contains !.

  1. First run an equivalent command without the exclamation mark: printf 'p4-start %s p4-end\n' 'x', which returns normally.
  2. Then run printf 'p4-start %s p4-end\n' '!', which hangs.
  3. Look at the delivery shape: the tool layer wraps the command into a string like the one below (with START/END markers and eval --).
  4. Feed that string to a bash --noprofile --norc -i matching production, and the output is:
bash: !\: event not found
dsh>
  1. Note two things: the START printf, the eval, and the END printf never ran; and the prompt after the error has no completion marker in front of it.
  2. Conclusion: the command is not wrong; the shell discarded the whole line at the read-line stage.

DSH plugin: Experiment 2: it is the quoting form, not the command

$'...' and single quotes treat ! differently: history expansion exempts content inside single quotes, not content inside ANSI-C quoting.

  1. Look at quoteForBash: it escapes backslashes, single quotes, \r, and \n, but never !.
  2. Look at the shell launch parameters: --noprofile --norc -i is interactive mode, where histexpand is on by default.
  3. Look at the initialization sequence: only stty -echo, with no history expansion disable.
  4. So a ! followed by an escaped single quote is read as the event identifier !\; the lookup fails, event not found is printed, and the whole line is discarded.
  5. This narrows the scope to the "quoting form": any change to the command content is only a bypass, not a fix.

DeepSeek Harness: Three layers of amplification: why a dropped line burns 300 seconds

The trigger is only the first layer; the other two are what actually burn the 300 seconds — the prompt lacks a completion marker, and the tool loop does not recognize that state.

  1. Layer one (trigger): history expansion discards the line, taking both START and END marker printfs with it.
  2. Layer two (amplification): completion detection relies on PROMPT_COMMAND printing OSC 133;D, but after a discarded line the prompt bash reprints does not run PROMPT_COMMAND, so only a bare prompt appears.
  3. Layer three (spinning): the tool loop's return branches recognize only "END marker seen", "session exited", and "wait reason is stdin_read"; inferred_idle is not among them, so it spins every 25 ms until the 300-second deadline.
  4. On timeout it takes the timeout branch and calls shells.reset() to kill the entire shell.
  5. What you see: a screen full of dsh>, the event not found cut off by the scrollback limit, and a notice that the beginning was discarded.

DSH Plugin: Experiment 3: test two fixes

One changes the quoting form, one turns off history expansion; both make the command return normally, but only the latter reached master.

OptionLayerChangeCommunity result
Blayer onereplace $'...' with traditional single-quote quotingfull chain outputs normally
Clayer oneappend set +H to initializationfull chain outputs normally
Dlayer threeexit early with a notice when spinning without output growthresilience hardening
Elayer twostop relying only on PROMPT_COMMAND for completionresilience hardening

Option B must keep handling literal \r and \n; option C is one line and is the root-cause fix. D and E do not remove the trigger but make future "line discarded" cases fail fast instead of burning 300 seconds.

DeepSeek Harness: Version status: fixed on master, not backported to rc.3

Commit 967ba4c4b8 (2026-10-21) changes initialization from stty -echo to stty -echo; set +H and adds a regression test, but it is not an ancestor of the rc.3 tag.

  1. Use git merge-base --is-ancestor 967ba4c4b8 <rc.3 commit> to check containment yourself.
  2. Community conclusion: it is an ancestor of master (22ddee3), not of dsh-v0.2.1-rc.3 (1f81c04).
  3. So rc.3 still reproduces it 100% of the time.
  4. Even with that fix merged, layer one is unchanged: quoteForBash still escapes only backslashes, single quotes, and newlines, and set +H is the only thing standing in front of !.
  5. In other words, if the backend or the wrapper layer is rewritten later, this quoting defect returns immediately.

DSH plugin: Today's workaround: keep bare ! out of commands

Until the fix reaches your version, the safest move is to keep the exclamation mark from ever reaching history expansion.

  1. Avoid a bare ! in commands; use an equivalent form such as printf to generate the literal.
  2. If you must include it, make sure it sits inside traditional single-quote quoting rather than ANSI-C quoting.
  3. Do not put set +H at the start of a command line expecting it to work — expansion happens at read-line time, before that line executes.
  4. Split long tasks into several short commands so a hang can be traced to a specific one quickly.
  5. After a hang, do not retry the same character repeatedly; switch to an equivalent form first to verify the link itself works.

Cautions

Avoid the trigger instead of fighting the timeout.

  1. Do not read the 300 seconds as a slow network. The timeout here is a local shell spinning, not a request timeout.
  2. Do not look only at the last screen. event not found may already be cut off by scrollback.
  3. Do not expect the session to recover. The timeout resets the shell and loses the previous interaction state.
  4. Record the bash version. The community reproduced it on macOS's bundled 3.2.57; 5.x behavior was not verified locally, and the fix should not depend on the version.
  5. Do not treat B and C as alternatives. One is correct quoting, the other disables expansion; defense in depth should do both.

If your command contains no ! at all and still hangs to timeout, it is not this class — check completion detection and shell initialization.

One last diagnostic trick: failures where "the shell discarded the command" usually end as "markers never appear + output is empty + it hits exactly the timeout ceiling". Remember that combination and you can skip a lot of guessing.

Likewise, a ! inside a heredoc takes the same path — expansion happens when the whole line is read, regardless of whether the command spans multiple lines.See DeepSeek Harness terminal: PSReadLine ghosts in context for the related case.

FAQ

Why does a bash command containing ! in DeepSeek Harness hang for 300 seconds?

DeepSeek Harness hangs because its command delivery layer wraps commands in $'...', while the controlled shell is an interactive bash with history expansion on; $'...' does not exempt !, so bash discards the whole line with event not found and never prints the completion marker, leaving the tool spinning until timeout.

Why do I only see a screen of dsh> prompts and no error in DeepSeek Harness?

You only see prompts because, after the line is discarded in DeepSeek Harness, the START marker printf never runs either, so on timeout the tool cannot find the start marker and falls back to the scrollback tail; the real event not found is cut by the scrollback limit, leaving only bare prompts.

Does adding set +H in the command fix it on the spot in DeepSeek Harness?

No, set +H in the command does not fix it on the spot in DeepSeek Harness: history expansion happens in the read-line stage, before the in-line statement executes; a set +H from a previous call is also wiped by the shell reset after a timeout. The only effective approach is to avoid a bare !.

Which DeepSeek Harness version contains the `set +H` fix?

The fix is master's commit 967ba4c4b8 in DeepSeek Harness, which changes initialization from stty -echo to stty -echo; set +H, but it is an ancestor of master and not of the rc.3 tag, so rc.3 still reproduces it.

How do I work around it on rc.3 in DeepSeek Harness today?

Until the fix ships, the DeepSeek Harness rc.3 workaround is to put ! inside traditional single quotes and make sure the whole command is single-quoted, or use an equivalent form without ! (for example, printf to generate a literal). Splitting long commands into shorter ones also exposes the failure faster.

Related Terms

history expansion
History expansion is bash's mechanism that replaces !-prefixed text with a history event while reading a line. It runs before parsing and execution, so disabling it inside the same line comes too late.— https://github.com/deepseek-ai/deepseek-harness/discussions/10636
quoteForBash
quoteForBash is the tool-layer function that wraps a command into a deliverable string using ANSI-C quoting `$'...'`. It escapes backslashes, single quotes, and newlines, but does not handle the exclamation mark.— https://github.com/deepseek-ai/deepseek-harness/discussions/10636
OSC 133;D
OSC 133;D is the marker printed by PROMPT_COMMAND in the terminal completion-detection protocol, indicating a command has ended. After a line is discarded, the prompt bash reprints lacks it, so completion detection fails.— https://github.com/deepseek-ai/deepseek-harness/discussions/10636
inferred_idle
inferred_idle is the fallback state of completion detection meaning the terminal looks idle but no completion marker arrived. The current tool loop does not treat it as an early-exit condition, so it waits until timeout.— https://github.com/deepseek-ai/deepseek-harness/discussions/10636

Sources