{"record":{"id":"b700815d51a9e6b0","repo":"pbakaus/impeccable","slug":"steer-done-requires-file-or-message","errorCode":"steer_done_requires_file_or_message","errorMessage":"steer_done_requires_file_or_message","messagePattern":"steer_done_requires_file_or_message","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"skill/scripts/live-server.mjs","lineNumber":1333,"sourceCode":"    if (msg.type === 'retry') {\n      const releasedEvent = releasePendingEvent(msg.id, sourceEventType);\n      if (!releasedEvent) {\n        res.writeHead(msg.id ? 404 : 400, { 'Content-Type': 'application/json' });\n        res.end(JSON.stringify({\n          error: msg.id ? 'unknown_poll_retry_id' : 'missing_poll_retry_id',\n          id: msg.id,\n        }));\n        return;\n      }\n      flushPendingPolls();\n      res.writeHead(200, { 'Content-Type': 'application/json' });\n      res.end(JSON.stringify({ ok: true, released: true }));\n      return;\n    }\n    const pendingEventBeforeAck = findPendingEventById(msg.id, sourceEventType);\n    if (pendingEventBeforeAck?.type === 'steer' && msg.type === 'steer_done'\n        && !msg.file && !(typeof msg.message === 'string' && msg.message.trim())) {\n      res.writeHead(400, { 'Content-Type': 'application/json' });\n      res.end(JSON.stringify({\n        error: 'steer_done_requires_file_or_message',\n        hint: 'Reply with --file after writing source, or include a message explaining an intentional no-op.',\n      }));\n      return;\n    }\n    const acknowledgedEvent = acknowledgePendingEvent(msg.id, sourceEventType);\n    let skipJournalReply = false;\n    let existingSession = null;\n    if (!acknowledgedEvent && state.sessionStore && msg.id) {\n      try {\n        existingSession = state.sessionStore.getSnapshot(msg.id, { includeCompleted: true });\n        if (!existingSession?.updatedAt) existingSession = null;\n        skipJournalReply = existingSession?.phase === 'completed' || existingSession?.phase === 'discarded';\n      } catch { /* fall through and record the reply normally */ }\n    }\n    if (!acknowledgedEvent && !existingSession) {\n      recordManualEditActivity('manual_edit_poll_reply_unknown', {","sourceCodeStart":1315,"sourceCodeEnd":1351,"githubUrl":"https://github.com/pbakaus/impeccable/blob/f88b2837a7d7c3182e46307bbbb091a1ed547571/skill/scripts/live-server.mjs#L1315-L1351","documentation":"When the event being acknowledged is a steer request and the reply type is steer_done, the server requires evidence of work: either msg.file (the source file the agent wrote) or a non-empty trimmed message string explaining an intentional no-op. Missing both yields HTTP 400 with error steer_done_requires_file_or_message and a hint repeating exactly those two options.","triggerScenarios":"Running live-poll.mjs --reply <id> done for a steer event without --file and without --message; passing --message '   ' (whitespace-only); passing the file in the wrong field name (e.g. filePath instead of file).","commonSituations":"The agent concludes the steer needs no code change but forgets to explain the no-op; the agent edits a file but replies before attaching --file; shell quoting eats an empty-but-intended message.","solutions":["If source was written: resend with the file attached, e.g. --reply <id> done --file src/Button.svelte","If intentional no-op: resend with a non-empty --message 'No change needed: X already does Y'","Check the pending event type first — this rule only fires for steer events acknowledged as steer_done"],"exampleFix":"// before\nlive-poll.mjs --reply evt_9 done\n// after\nlive-poll.mjs --reply evt_9 done --file src/hero/Hero.svelte\n// or: live-poll.mjs --reply evt_9 done --message 'No-op: requested variant already the default'","handlingStrategy":"validation","validationCode":"// Before sending steer_done, ensure evidence of work is attached\nconst ok = Boolean(msg.file) || (typeof msg.message === 'string' && msg.message.trim().length > 0);\nif (!ok) throw new Error('attach --file or a non-empty --message before steer_done');","typeGuard":"function hasFileOrMessage(m) {\n  return Boolean(m?.file) || (typeof m?.message === 'string' && m.message.trim().length > 0);\n}","tryCatchPattern":null,"preventionTips":["Make 'no-op needs a reason' a habit: every steer_done carries either the file or one sentence","Trim and assert message length > 0, whitespace does not count","Check the pending event type before choosing the reply shape"],"tags":["steer","reply-validation","live-server","http-400"],"backgroundTag":"missing-required-field","analyzedSha":"f88b2837a7d7c3182e46307bbbb091a1ed547571","analyzedAt":"2026-08-18T04:58:36.608Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}