Repository navigation
Description
When the AI CLI exits nonzero or returns empty output, generate_with_memory catches the failure and returns None after retries (
RPG-ZeroRepo/CoderMind/scripts/common/llm_client.py
Lines 469 to 479 in f094772
| if result.returncode != 0: | |
| error = f"LLM call failed with return code {result.returncode}: {result.stderr}" | |
| self.logger.warning(error) | |
| continue | |
| response = result.stdout.strip() | |
| if response: | |
| break | |
| else: | |
| error = "LLM returned empty response" | |
| self.logger.warning(error) |
RPG-ZeroRepo/CoderMind/scripts/common/llm_client.py
Lines 527 to 528 in f094772
| if not response: | |
| raise RuntimeError(f"Failed to get response from LLM after {max_retries} retries: {error}") |
RPG-ZeroRepo/CoderMind/scripts/common/llm_client.py
Lines 594 to 648 in f094772
| def generate_with_memory( | |
| self, | |
| memory: Memory, | |
| purpose: str = "general", | |
| max_retries: int = 3, | |
| timeout: Optional[int] = 1800, | |
| metadata: Dict[str, Any] = None, | |
| ) -> Optional[str]: | |
| """Generate from a Memory object by flattening to a single prompt. | |
| Encoder and RPGAgent use Memory to manage multi-turn conversations. | |
| This method flattens the message sequence into a single prompt | |
| string, then invokes the CLI subprocess via ``generate()``. | |
| Differences from ``generate()``: | |
| - Input: Memory object (multiple messages) instead of a single string | |
| - Return: ``Optional[str]`` (None on failure) instead of raising | |
| Args: | |
| memory: Memory instance with SystemMessage / UserMessage / | |
| AssistantMessage entries. | |
| purpose, max_retries, timeout, metadata: Same as ``generate()``. | |
| Returns: | |
| LLM response text, or None if all retries failed. | |
| Raises: | |
| RuntimeError: only when ``self.tool`` is not configured. | |
| Genuine LLM-call failures (subprocess errors, timeouts, | |
| bad responses) are swallowed and surface as ``None``; | |
| missing configuration is an unrecoverable user-action | |
| error and must not be silently masked. | |
| """ | |
| # Eagerly surface the "AI CLI not configured" condition. This is | |
| # not a transient failure that ``None`` should represent — the | |
| # user needs an actionable error. Genuine LLM failures continue | |
| # to be caught below. | |
| if not self.tool or self.tool == _PLACEHOLDER_LITERAL: | |
| raise RuntimeError( | |
| "AI CLI command not configured. Run " | |
| "`cmind init --ai <name>` in this workspace, or set the " | |
| "CMIND_AI_CLI_CMD environment variable." | |
| ) | |
| prompt = self._flatten_memory(memory) | |
| try: | |
| return self.generate( | |
| prompt=prompt, | |
| purpose=purpose, | |
| max_retries=max_retries, | |
| timeout=timeout, | |
| metadata=metadata, | |
| ) | |
| except RuntimeError: | |
| return None |
parse_solution_output(None) then raises 'NoneType' object has no attribute 'split' each functional-area iteration; the loop returns an empty plan and AssertionError: functional_areas must not be empty aborts before run_encode reaches its dependency-graph build/save steps, so no dependency graph is saved (RPG-ZeroRepo/CoderMind/scripts/common/utils.py
Lines 761 to 776 in f094772
| def parse_solution_output(output: str) -> str: | |
| """Extract the content inside ``<solution>...</solution>`` tags. | |
| If the tags are not present the full (stripped) output is returned. | |
| Args: | |
| output: Raw LLM output string. | |
| Returns: | |
| Extracted solution text, stripped of leading/trailing whitespace. | |
| Source: RPG-ZeroRepo/zerorepo/utils/api.py (parse_solution_output) | |
| """ | |
| output = output.split("<solution>", 1)[-1] | |
| output = output.split("</solution>", 1)[0] | |
| return output.strip() |
RPG-ZeroRepo/CoderMind/scripts/rpg_encoder/refactor_tree.py
Lines 516 to 543 in f094772
| for i in range(max_iters): | |
| try: | |
| response = self.llm_client.generate_with_memory(memory) | |
| if i == 0: | |
| memory.add_message(AssistantMessage(response)) | |
| parsed_response = parse_solution_output(response) | |
| parsed_response = ( | |
| parsed_response.replace("```json", "") | |
| .replace("```", "") | |
| .replace("\n", "") | |
| .replace("\t", "") | |
| ) | |
| parsed_result = json.loads(parsed_response) | |
| candidate_results.append(parsed_result) | |
| memory.add_message(AssistantMessage(response)) | |
| followup_prompt = ( | |
| f"Here is your plan iteration {i + 1}. " | |
| "If you believe there are missing or redundant functional areas, refine the grouping. " | |
| "Return only a corrected JSON." | |
| ) | |
| memory.add_message(UserMessage(followup_prompt)) | |
| except Exception as e: | |
| self.logger.warning("Iteration %d failed: %s", i + 1, e) | |
| continue |
RPG-ZeroRepo/CoderMind/scripts/rpg_encoder/refactor_tree.py
Lines 581 to 585 in f094772
| else: | |
| self.logger.warning("No valid results from LLM - returning empty dict.") | |
| final_result = {} | |
| return {"candidates": candidate_results, "final_plan": final_result} |
RPG-ZeroRepo/CoderMind/scripts/rpg_encoder/refactor_tree.py
Lines 769 to 773 in f094772
| functional_result = self.plan_functional_areas( | |
| parsed_tree=parsed_tree, max_iters=3 | |
| ) | |
| functional_areas = functional_result.get("final_plan", []) | |
| assert functional_areas, "functional_areas must not be empty" |
RPG-ZeroRepo/CoderMind/scripts/rpg_encoder/run_encode.py
Lines 89 to 92 in f094772
| rpg, feature_tree, skeleton_info = parser.parse_rpg_from_repo( | |
| save_path=output, | |
| max_exclude_votes=max_exclude_votes, | |
| ) |
RPG-ZeroRepo/CoderMind/scripts/rpg_encoder/run_encode.py
Lines 124 to 135 in f094772
| # Step 3: Save RPG to disk | |
| step_save = traj.add_step("save_rpg", "Save RPG to disk") | |
| traj.start_step(step_save.step_id) | |
| result_data = rpg.to_dict() | |
| # Atomic write of the central pipeline artefact. A killed | |
| # encode used to truncate rpg.json and brick downstream | |
| # stages (skeleton / func_design / code_gen all read it); | |
| # now the previous good rpg.json survives any interrupted | |
| # write. | |
| atomic_write_rpg(output, result_data, indent=2, ensure_ascii=False) |
This reproduced with the Claude backend on a 7-file Python repo. The workspace had a .cmind/config.toml matching the documented cmind init --ai claude layout; cmind init itself was not run. The /cmind.encode command failed on all three functional-area requests. Please surface the final CLI exit code and a concise stderr summary when generate_with_memory exhausts its retries, rather than returning None and failing later. The open PR at #99 touches llm_client.py, so maintainers could fold this in there.
This issue was AI-generated at the human operator's direction and reviewed by the operator before submission.
Activity