From 8f2ec48e10ddfd83532b3903bb1f8dc1647cf34d Mon Sep 17 00:00:00 2001 From: PeninsulaInd Date: Sun, 8 Mar 2026 14:38:48 -0500 Subject: [PATCH] Increase CLI timeout to 15min, fix result file reprocessing, and save outlines directly - Bump Claude Code subprocess timeout from 5 to 15 minutes for longer content tasks - Fix scheduler result file loop: unlink source if already exists in processed/ dir - Pass outline save path to execution brain so it writes directly to network share Co-Authored-By: Claude Opus 4.6 --- cheddahbot/llm.py | 4 +-- cheddahbot/scheduler.py | 3 ++ cheddahbot/tools/content_creation.py | 48 ++++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/cheddahbot/llm.py b/cheddahbot/llm.py index 747a4f0..17ae905 100644 --- a/cheddahbot/llm.py +++ b/cheddahbot/llm.py @@ -218,10 +218,10 @@ class LLMAdapter: ) try: - stdout, stderr = proc.communicate(input=prompt, timeout=300) + stdout, stderr = proc.communicate(input=prompt, timeout=900) except subprocess.TimeoutExpired: proc.kill() - return "Error: Claude Code execution timed out after 5 minutes." + return "Error: Claude Code execution timed out after 15 minutes." if proc.returncode != 0: return f"Execution error: {stderr or 'unknown error'}" diff --git a/cheddahbot/scheduler.py b/cheddahbot/scheduler.py index 548037d..5f8e024 100644 --- a/cheddahbot/scheduler.py +++ b/cheddahbot/scheduler.py @@ -653,6 +653,9 @@ class Scheduler: result_path.rename(processed_dir / result_path.name) except OSError as e: log.warning("Could not move result file %s: %s", result_path.name, e) + # If it already exists in processed/, delete the source to stop reprocessing + if (processed_dir / result_path.name).exists(): + result_path.unlink(missing_ok=True) # ── Folder Watcher ── diff --git a/cheddahbot/tools/content_creation.py b/cheddahbot/tools/content_creation.py index 38d8f3f..72879c3 100644 --- a/cheddahbot/tools/content_creation.py +++ b/cheddahbot/tools/content_creation.py @@ -228,6 +228,7 @@ def _build_phase1_prompt( cora_path: str, capabilities_default: str, is_service_page: bool = False, + outline_save_path: str = "", ) -> str: """Build the Phase 1 prompt that triggers the content-researcher skill. @@ -286,10 +287,19 @@ def _build_phase1_prompt( f'\nWhen asked about company capabilities, respond with: "{capabilities_default}"' ) - parts.append( - "\nDeliver the outline as a complete markdown document with sections, " - "headings, entity targets, and keyword placement notes." - ) + if outline_save_path: + parts.append( + f"\nSave the finished outline to `{outline_save_path}`. " + "Create any missing directories first. " + "The outline must be a complete markdown document with sections, " + "headings, entity targets, and keyword placement notes. " + "Do NOT save it anywhere else." + ) + else: + parts.append( + "\nDeliver the outline as a complete markdown document with sections, " + "headings, entity targets, and keyword placement notes." + ) return "\n".join(parts) @@ -847,12 +857,29 @@ def _run_phase1( capabilities_default: str, is_service_page: bool = False, ) -> str: + # Compute the outline save path upfront so the execution brain writes + # directly to the network share (or local fallback). + slug = _slugify(keyword) or "unknown" + outline_path = "" + if config.content.outline_dir: + primary = Path(config.content.outline_dir) / slug + try: + primary.mkdir(parents=True, exist_ok=True) + outline_path = str(primary / "outline.md") + except OSError as e: + log.warning("Network path unavailable (%s), falling back to local: %s", primary, e) + if not outline_path: + local = _LOCAL_CONTENT_DIR / slug + local.mkdir(parents=True, exist_ok=True) + outline_path = str(local / "outline.md") + # ClickUp: move to automation underway if task_id: _sync_clickup_start(ctx, task_id) prompt = _build_phase1_prompt( - url, keyword, content_type, cora_path, capabilities_default, is_service_page + url, keyword, content_type, cora_path, capabilities_default, is_service_page, + outline_save_path=outline_path, ) log.info("Phase 1 — researching + outlining for '%s' (%s)", keyword, url or "new content") @@ -874,8 +901,15 @@ def _run_phase1( _sync_clickup_fail(ctx, task_id, result) return result - # Save the outline - outline_path = _save_content(result, keyword, "outline.md", config) + # Verify the outline was saved by the execution brain + if not Path(outline_path).is_file(): + log.warning( + "Execution brain did not save outline to %s; saving result text as fallback.", + outline_path, + ) + Path(outline_path).parent.mkdir(parents=True, exist_ok=True) + Path(outline_path).write_text(result, encoding="utf-8") + log.info("Outline saved to: %s", outline_path) # ClickUp: move to outline review + store OutlinePath