diff --git a/internal/loop/opencode_parser.go b/internal/loop/opencode_parser.go
index a9e81ae8..31e536e6 100644
--- a/internal/loop/opencode_parser.go
+++ b/internal/loop/opencode_parser.go
@@ -110,12 +110,11 @@ func ParseLineOpenCode(line string) *Event {
}
case "step_finish":
- if ev.Part == nil {
- return nil
- }
- if ev.Part.Reason == "stop" {
- return &Event{Type: EventComplete}
- }
+ // step_finish signals the end of a single agent step, not the end of the PRD.
+ // Per-story completion is signaled via the text marker (handled in
+ // the "text" case above). Overall PRD completion is emitted by the loop when
+ // NextStory() returns nil. Mapping reason=="stop" to EventComplete here would
+ // fire the completion screen after every story.
return nil
case "error":
diff --git a/internal/loop/opencode_parser_test.go b/internal/loop/opencode_parser_test.go
index 4d9f97ca..bd70eb0f 100644
--- a/internal/loop/opencode_parser_test.go
+++ b/internal/loop/opencode_parser_test.go
@@ -77,11 +77,8 @@ func TestParseLineOpenCode_text(t *testing.T) {
func TestParseLineOpenCode_stepFinishStop(t *testing.T) {
line := `{"type":"step_finish","timestamp":1767036064273,"sessionID":"ses_494719016ffe85dkDMj0FPRbHK","part":{"id":"prt_b6b8e9209001ojZ4ECN1geZISm","sessionID":"ses_494719016ffe85dkDMj0FPRbHK","messageID":"msg_b6b8e8627001yM4qKJCXdC7W1L","type":"step-finish","reason":"stop","snapshot":"09dd05d11a4ac013136c1df10932efc0ad9116e8","cost":0.001,"tokens":{"input":671,"output":8,"reasoning":0,"cache":{"read":21415,"write":0}}}}`
ev := ParseLineOpenCode(line)
- if ev == nil {
- t.Fatal("expected event, got nil")
- }
- if ev.Type != EventComplete {
- t.Errorf("expected EventComplete, got %v", ev.Type)
+ if ev != nil {
+ t.Errorf("expected nil (step_finish does not signal PRD completion; + buildPrompt drive completion), got %v", ev)
}
}