DEV Community

Ahab
Ahab

Posted on Originally published at indieseek.co

Qwen Code 0.24.1 stops emitting active_goal: migrate headless Goal streams safely

Qwen Code 0.24.1 stops emitting active_goal: migrate headless Goal streams safely

Quick answer

Qwen Code v0.24.1 removes the active_goal event from headless --output-format stream-json. If your CI dashboard, SDK wrapper, or log parser waits for event.type === "active_goal", it can appear to lose Goal progress after upgrading even when the Goal itself continues. Read the authoritative goal_state event instead. Qwen's release lists this as a breaking change; its Headless Mode documentation says goal_state is emitted for each Goal status change with or without --include-partial-messages.

Do not map “no matching event” to “no active Goal.” First identify your transport, update the event filter, then replay old and new fixtures. This is a consumer migration, not a reason to reset Goal state or weaken automation limits.

Who this is for

This guide is for teams consuming Qwen Code's NDJSON headless stream, especially CI jobs, terminal dashboards, SDK adapters, and alerting pipelines that monitor long-running Goals. It is not a general migration for every Qwen Code user: the interactive UI did not consume the retired projection. ACP clients such as IDE integrations and Web Shell use a different wire surface, session/update with _meta.goalState, rather than a headless goal_state stream event.

Our earlier Qwen Code Goal budget rollout guide explains how to set and verify turn/time limits. This article solves a separate problem: preserving correct progress and terminal-state reporting when an integration upgrades to v0.24.1.

What changed, and what did not

The old active_goal event was a compatibility projection of goal_state into a previous card format. Qwen's merged change review says it followed goal_state only when partial messages were enabled; the two terminal UIs ignored it. The v0.24.1 release removes that projection and related exported types, including LlmEventType.ActiveGoal and ActiveGoalStreamEvent. It does not say that Goals or goal_state were removed.

Consumer Old assumption v0.24.1 action
Headless stream-json parser Wait for active_goal Match type: stream_event, then event.type: goal_state.
Headless run without partial messages Enable partial messages to see a Goal event goal_state already arrives without that switch; keep partial messages only if other consumers need token deltas.
Typed wrapper Import ActiveGoal or ActiveGoalStreamEvent Move to the current Goal-state type and compile against the pinned version. Do not cast away the error.
ACP IDE/Web Shell client Expect headless NDJSON event Handle session/update with _meta.goalState on the ACP path.
Human-facing terminal Depend on old projection No direct parser change unless your own extension read the stream.

Avoid pretending the old and new payloads have identical fields. Treat the complete goal_state event as the new source of truth and adapt your own view model explicitly. Record the Qwen Code version, transport, session ID, event type, and final process result separately.

A bounded migration workflow

  1. Locate every reader. Search code, jobs, dashboards, schema validators, tests, and saved-stream replayers for active_goal, ActiveGoalStreamEvent, and LlmEventType.ActiveGoal. A green compile in one package does not prove a separate log processor was updated.
  2. Pin the rollout. Keep the current working CLI and a v0.24.1 test build in a disposable project. Record the exact binary version and whether the route is headless NDJSON or ACP. Do not compare unlike transports.
  3. Replace the filter. For NDJSON, inspect the outer message and inner event before updating state. This minimal predicate is intentionally independent of undocumented Goal payload fields:
   function goalStateEvent(message) {
     return message?.type === 'stream_event' &&
       message.event?.type === 'goal_state'
       ? message.event : null;
   }
Enter fullscreen mode Exit fullscreen mode
  1. Replay a two-version fixture. Feed one recorded old stream and one new stream into the same adapter. Assert that the new stream produces exactly one state update per goal_state record, not a duplicate from a legacy projection. Preserve unknown events for diagnostics; do not turn them into Goal completion.
  2. Test a controlled Goal transition. In a trusted disposable workspace and account, create a small bounded Goal, inspect it, pause it, and resume or clear it deliberately. Compare observed status changes, not merely whether the final answer text looks right. The official docs note that a headless Goal is tied to a recorded session; use --continue or --resume for later controls, with chat recording enabled.
  3. Gate promotion. Check partial messages on and off, a non-Goal run, malformed NDJSON, process error, and an interrupted session. A parser should not announce “complete” because the event stream went quiet. Keep your existing budget and human-action controls.

The read-only diagnostic shape is qwen --continue -p "/goal" --output-format stream-json. To inspect the recorded stream, filter the nested type: jq -c 'select(.type == "stream_event" and .event.type == "goal_state") | .event'. Run Goal creation only in an approved test account: it may initiate autonomous work and consume model usage.

Acceptance matrix and reusable receipt

Canary Required evidence Failure interpretation
New CLI, partial messages on goal_state observed; no active_goal If missing, inspect command/session/transport before claiming no Goal.
New CLI, partial messages off goal_state still observed If not, check whether a status transition actually occurred.
Old recorded stream Adapter handles goal_state once and ignores duplicate projection Two UI updates reveal double counting.
ACP client _meta.goalState on session/update NDJSON filter must not be reused on this wire.
Error or interrupted run Separate error/exit evidence, last known Goal state preserved Silence must not become a false success or false clear.
qwen_version: 
transport: headless_stream_json | acp
session_id: 
partial_messages: on | off
goal_state_events_seen: 
active_goal_events_seen: 
state_transitions_checked: create | inspect | pause | resume | clear
process_result: success | error | interrupted
adapter_result: pass | fail
owner_and_decision: 
Enter fullscreen mode Exit fullscreen mode

Common mistakes

  • Treating absence of active_goal as a stopped Goal. Only the compatibility event disappeared.
  • Enabling partial messages to “restore” the old event. v0.24.1 does not emit it; goal_state is available without the flag.
  • Conflating ACP's _meta.goalState with headless NDJSON's event.type: goal_state.
  • Parsing a payload field by guessed name or claiming a verified live run from documentation alone.
  • Turning a stream parser change into an authorization change. The Goal's permission and budget boundaries remain separate.

Building something? Turn your product page into a show people want to watch with PromoFast—hosted, embeddable, and ready to export.

FAQ

Does v0.24.1 remove Qwen Code Goals?

No. The breaking change removes a redundant active_goal stream event and old types. The official headless guide still documents Goals and the goal_state status event.

Do I need --include-partial-messages?

Not for goal_state. Keep it only if your application also needs partial token or content events.

Can I use the same handler in an IDE integration?

Not unchanged. ACP sessions report Goal status through session/update and _meta.goalState; confirm which transport your host actually uses.

Sources

Originally published on IndieSeek.

Top comments (0)