{"record":{"id":"444d7a925478e416","repo":"shareAI-lab/learn-claude-code","slug":"background-result-cannot-be-empty","errorCode":null,"errorMessage":"background result cannot be empty","messagePattern":"background result cannot be empty","errorType":"validation","errorClass":"GoalError","httpStatus":null,"severity":"error","filePath":"s17_goal_loop/code.py","lineNumber":653,"sourceCode":"    def _summary_hook(messages: list[dict[str, Any]]) -> None:\n        tool_count = sum(\n            1\n            for message in messages\n            for block in (\n                message.get(\"content\")\n                if isinstance(message.get(\"content\"), list)\n                else []\n            )\n            if isinstance(block, dict) and block.get(\"type\") == \"tool_result\"\n        )\n        print(f\"[hook] Stop: session used {tool_count} tool calls\")\n        return None\n\n    async def submit_background_result(self, text: str) -> SessionResult:\n        \"\"\"Resume an active goal after the host receives background output.\"\"\"\n\n        if not text.strip():\n            raise GoalError(\"background result cannot be empty\")\n        self.messages.append(\n            {\n                \"role\": \"user\",\n                \"content\": f\"[Background task completed]\\n{text}\",\n            }\n        )\n        if self.goal.active is None:\n            return SessionResult(text=\"\", status=\"background_result\")\n        self.goal.begin_query()\n        return await self._run_query()\n\n    async def _run_query(self) -> SessionResult:\n        turns = 0\n        while True:\n            if self.max_turns is not None and turns >= self.max_turns:\n                self.trigger_hooks(\"Stop\", self.messages)\n                return SessionResult(\n                    text=\"\",","sourceCodeStart":635,"sourceCodeEnd":671,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s17_goal_loop/code.py#L635-L671","documentation":"AgentSession.submit_background_result was called with text that is empty after stripping (s17_goal_loop/code.py:653). This method resumes the loop by appending a '[Background task completed]' user message carrying the background output; an empty body would resume the agent with no new information, so it is rejected.","triggerScenarios":"Calling submit_background_result(\"\") or submit_background_result(\"   \\n  \"); forwarding a background process's stdout that was empty or only whitespace; calling it before the background task produced output.","commonSituations":"Host process wakes on a background-job-exit event and forwards captured stdout without checking it; background command failed with output on stderr only; race where the result is submitted before the writer flushed.","solutions":["Capture and check output before submitting; if empty, surface stderr or the exit code to the agent in the text","Include the exit status in the text so it is never blank: f'exit={rc}\\n{stdout}'","If the background task genuinely produced nothing, submit a meaningful sentinel like '(no output, exit 0)' instead of an empty string"],"exampleFix":"# before\nawait session.submit_background_result(proc.stdout)\n\n# after\nstdout, stderr = proc.communicate()\ntext = stdout.strip() or stderr.strip() or f\"(no output; exit {proc.returncode})\"\nawait session.submit_background_result(text)","handlingStrategy":"validation","validationCode":"text = (stdout or \"\").strip()\nif not text:\n    text = (stderr or \"\").strip() or f\"(no output; exit {returncode})\"\nawait session.submit_background_result(text)","typeGuard":"def is_submittable_background_result(text: object) -> bool:\n    return isinstance(text, str) and bool(text.strip())","tryCatchPattern":"try:\n    await session.submit_background_result(text)\nexcept GoalError:\n    text = \"(background task produced no output)\"\n    await session.submit_background_result(text)","preventionTips":["Always compose the payload with exit status so it can never be blank","Submit stderr when stdout is empty; failing commands log there","Guard the resume call at the host boundary rather than letting whitespace through"],"tags":["goal-loop","background-tasks","validation"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}