Description
When a provider ends a turn with the content-filter finish reason, the session loop exits silently and the session goes idle. Nothing is surfaced to the TUI — no error, no notice, no status. The turn just stops mid-work, leaving an assistant message with a handful of output tokens and no text parts. It looks exactly like CyberStrike hung.
Sending any new user message resumes work normally, which makes the failure look intermittent and random.
The loop exit condition only treats tool-calls and unknown as continuable:
if (lastAssistant?.finish && !["tool-calls", "unknown"].includes(lastAssistant.finish) && lastUser.id < lastAssistant.id) {
log12.info("exiting loop", { sessionID });
content-filter falls through this check, so the loop terminates. The exit is logged at INFO level (service=session.prompt ... exiting loop), not ERROR, so there is no signal anywhere the user can see. Searching the binary for content-filter turns up only provider stop-reason mappings — no retry, no error surfacing, no special handling.
This matters a lot for offensive-security workloads specifically: the provider safety classifier fires far more often on exploitation work than on ordinary coding, so this path gets hit constantly. In one 21-minute session I hit it 7 times.
Evidence
Single session, 116 messages. Distribution of finish across assistant messages (read from the message table in cyberstrike.db):
| finish |
count |
tool-calls |
100 |
content-filter |
7 |
null (in flight) |
1 |
Each content-filter event, every one a visible stall:
09:04:18 out=8 agent=cyberstrike model=claude-opus-5 variant=high
09:08:02 out=2 agent=cyberstrike model=claude-opus-5 variant=high
09:12:38 out=9 agent=cyberstrike model=claude-opus-5 variant=high
09:19:06 out=9 agent=cyberstrike model=claude-opus-5 variant=high
09:19:54 out=3 agent=cyberstrike model=claude-opus-5 variant=high
09:23:55 out=3 agent=general model=claude-opus-5 variant=high
09:25:53 out=1 agent=general model=claude-opus-5 variant=high
Every one produced 1–9 output tokens and zero parts of type text. Switching agents does not help — the last two happened after switching to general. What actually resumes the loop is the new user message, since it makes lastUser.id > lastAssistant.id and the exit condition stops matching.
Log tail at the moment of a stall (nothing after this until the client disconnected two minutes later):
INFO 09:04:21 service=session.prompt input=2 output=8 cacheRead=138198 cacheWrite=240 total=138448 contextLimit=968000 utilization=14 context usage
INFO 09:04:21 service=session.prompt step=85 sessionID=... loop
INFO 09:04:21 service=session.prompt sessionID=... exiting loop
INFO 09:04:21 service=session.prompt sessionID=... cancel
INFO 09:04:21 service=bus type=session.idle publishing
Ruled out while investigating:
- Not a process hang — both server processes sat in
State: S (sleeping), wchan: do_epoll_wait.
- Not SQLite lock contention — no process blocked on a lock, WAL healthy.
- Not context pressure —
utilization=14, well under the limit.
- Not the MCP
-32601 Method not found failed to get prompts startup warnings (cve, osint, github-security) — those fire once at startup and do not correlate with the stalls.
Steps to reproduce
- Run a session against a target where the provider's safety classifier will fire (any ordinary exploitation workflow with an Anthropic model does it within minutes).
- Wait for a turn to come back with
stop_reason: refusal, which the protocol layer maps to the content-filter finish reason.
- The turn ends with 1–9 output tokens and no text parts. The session goes idle with no error box, no notice, nothing rendered.
- Send any message and the loop resumes normally.
Upstream
This is already known and fixed in the upstream codebase:
CyberStrike 1.1.15 still has the pre-fix behavior. Since this repo is a detached copy rather than a GitHub fork, the upstream patch did not flow through.
Suggested fix
Either of these would remove the "it hung" experience:
- Surface it — map
content-filter to a visible assistant failure the way upstream core now does, so the TUI renders something instead of going quietly idle.
- At minimum, log the exit at
ERROR/WARN with the finish reason instead of INFO, so it is diagnosable without reading the message table out of SQLite.
Happy to test a patch against this workload — it reproduces within minutes here.
Agent
cyberstrike (default), also reproduced on general
Interface
CLI / TUI
LLM Provider
Anthropic
CyberStrike version
1.1.15
Operating System
Kali GNU/Linux Rolling 2026.3, kernel 7.0.6
Terminal
TUI running in a standard Linux terminal
Description
When a provider ends a turn with the
content-filterfinish reason, the session loop exits silently and the session goes idle. Nothing is surfaced to the TUI — no error, no notice, no status. The turn just stops mid-work, leaving an assistant message with a handful of output tokens and no text parts. It looks exactly like CyberStrike hung.Sending any new user message resumes work normally, which makes the failure look intermittent and random.
The loop exit condition only treats
tool-callsandunknownas continuable:content-filterfalls through this check, so the loop terminates. The exit is logged atINFOlevel (service=session.prompt ... exiting loop), notERROR, so there is no signal anywhere the user can see. Searching the binary forcontent-filterturns up only provider stop-reason mappings — no retry, no error surfacing, no special handling.This matters a lot for offensive-security workloads specifically: the provider safety classifier fires far more often on exploitation work than on ordinary coding, so this path gets hit constantly. In one 21-minute session I hit it 7 times.
Evidence
Single session, 116 messages. Distribution of
finishacross assistant messages (read from themessagetable incyberstrike.db):tool-callscontent-filternull(in flight)Each
content-filterevent, every one a visible stall:Every one produced 1–9 output tokens and zero parts of type
text. Switching agents does not help — the last two happened after switching togeneral. What actually resumes the loop is the new user message, since it makeslastUser.id > lastAssistant.idand the exit condition stops matching.Log tail at the moment of a stall (nothing after this until the client disconnected two minutes later):
Ruled out while investigating:
State: S (sleeping),wchan: do_epoll_wait.utilization=14, well under the limit.-32601 Method not found failed to get promptsstartup warnings (cve, osint, github-security) — those fire once at startup and do not correlate with the stalls.Steps to reproduce
stop_reason: refusal, which the protocol layer maps to thecontent-filterfinish reason.Upstream
This is already known and fixed in the upstream codebase:
bug: turn ending with content-filter finish reason stops silently. Closed as completed 2026-06-11 (reported against 1.17.3). Same mechanism, same symptom, right down to the reporter hitting it repeatedly in one session.V2: improve UX for provider content-filter finishes(open). Per its comments, PR #35272 landed and mapscontent-filterfinishes toprovider.content-filterassistant failures in core; UI PR #34834 for visible notices in TUI and web is still open.stop_reason:refusalall surfacing identically as content filter).CyberStrike 1.1.15 still has the pre-fix behavior. Since this repo is a detached copy rather than a GitHub fork, the upstream patch did not flow through.
Suggested fix
Either of these would remove the "it hung" experience:
content-filterto a visible assistant failure the way upstream core now does, so the TUI renders something instead of going quietly idle.ERROR/WARNwith the finish reason instead ofINFO, so it is diagnosable without reading the message table out of SQLite.Happy to test a patch against this workload — it reproduces within minutes here.
Agent
cyberstrike (default), also reproduced on general
Interface
CLI / TUI
LLM Provider
Anthropic
CyberStrike version
1.1.15
Operating System
Kali GNU/Linux Rolling 2026.3, kernel 7.0.6
Terminal
TUI running in a standard Linux terminal