Skip to content

[BUG]: content-filter finish reason exits the session loop silently — looks like a hang #62

Description

@Darcon11

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

  1. 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).
  2. Wait for a turn to come back with stop_reason: refusal, which the protocol layer maps to the content-filter finish reason.
  3. The turn ends with 1–9 output tokens and no text parts. The session goes idle with no error box, no notice, nothing rendered.
  4. 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:

  1. 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.
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions