Fix output order and add Reddit error handling

- SKILL.md: Move "What I learned" BEFORE "Research Complete" stats
- Add error tracking to Report schema (reddit_error, x_error fields)
- Wrap OpenAI API calls in try/catch with clear error messages
- Show explicit error or "no results" messages in compact output
- Fix false positive error detection for null error fields

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Matt Van Horn
2026-01-23 15:26:33 -08:00
parent 72e4f79d65
commit 40f9dc4877
5 changed files with 69 additions and 21 deletions
+5 -9
View File
@@ -108,20 +108,15 @@ Read the research output and become an **expert**. Identify:
## THEN: Show Summary + Invite Vision ## THEN: Show Summary + Invite Vision
Display in this EXACT order (so stats are visible at bottom of terminal): **CRITICAL ORDER**: Display sections in this EXACT sequence (insights FIRST, stats LAST):
``` ```
**Key patterns discovered:** ---
1. [Pattern 1] - [one-line insight] What I learned:
2. [Pattern 2] - [one-line insight]
3. [Pattern 3] - [one-line insight]
4. [Pattern 4] - [one-line insight]
5. [Pattern 5] - [one-line insight]
I'm now an expert in {TOPIC}. [2-4 sentences synthesizing the key insight from your research. What's the secret? What pattern emerged? What do experts do differently? Write this as a mini-expert briefing, not a list.]
--- ---
📊 Research Complete 📊 Research Complete
Analyzed {total_sources} sources from the last 30 days Analyzed {total_sources} sources from the last 30 days
@@ -129,6 +124,7 @@ Analyzed {total_sources} sources from the last 30 days
├─ X: {n} posts │ {sum} likes │ {sum} reposts ├─ X: {n} posts │ {sum} likes │ {sum} reposts
└─ Top voices: r/{sub1}, r/{sub2}, @{handle1}, @{handle2} └─ Top voices: r/{sub1}, r/{sub2}, @{handle1}, @{handle2}
---
Share your vision for what you want to create and I'll write a thoughtful prompt you can copy-paste directly into {TARGET_TOOL}. Share your vision for what you want to create and I'll write a thoughtful prompt you can copy-paste directly into {TARGET_TOOL}.
``` ```
+19 -3
View File
@@ -29,6 +29,7 @@ from lib import (
dates, dates,
dedupe, dedupe,
env, env,
http,
models, models,
normalize, normalize,
openai_reddit, openai_reddit,
@@ -62,25 +63,38 @@ def run_research(
"""Run the research pipeline. """Run the research pipeline.
Returns: Returns:
Tuple of (reddit_items, x_items, raw_openai, raw_xai, raw_reddit_enriched) Tuple of (reddit_items, x_items, raw_openai, raw_xai, raw_reddit_enriched, reddit_error, x_error)
""" """
reddit_items = [] reddit_items = []
x_items = [] x_items = []
raw_openai = None raw_openai = None
raw_xai = None raw_xai = None
raw_reddit_enriched = [] raw_reddit_enriched = []
reddit_error = None
x_error = None
# Reddit search via OpenAI # Reddit search via OpenAI
if sources in ("both", "reddit"): if sources in ("both", "reddit"):
if mock: if mock:
raw_openai = load_fixture("openai_sample.json") raw_openai = load_fixture("openai_sample.json")
else: else:
try:
raw_openai = openai_reddit.search_reddit( raw_openai = openai_reddit.search_reddit(
config["OPENAI_API_KEY"], config["OPENAI_API_KEY"],
selected_models["openai"], selected_models["openai"],
topic, topic,
depth=depth, depth=depth,
) )
except http.HTTPError as e:
print(f"[REDDIT ERROR] OpenAI API request failed: {e}", flush=True)
if e.body:
print(f"[REDDIT ERROR] Response body: {e.body[:500]}", flush=True)
raw_openai = {"error": str(e)}
reddit_error = f"API error: {e}"
except Exception as e:
print(f"[REDDIT ERROR] Unexpected error: {type(e).__name__}: {e}", flush=True)
raw_openai = {"error": str(e)}
reddit_error = f"{type(e).__name__}: {e}"
# Parse response # Parse response
reddit_items = openai_reddit.parse_reddit_response(raw_openai) reddit_items = openai_reddit.parse_reddit_response(raw_openai)
@@ -112,7 +126,7 @@ def run_research(
# Parse response # Parse response
x_items = xai_x.parse_x_response(raw_xai) x_items = xai_x.parse_x_response(raw_xai)
return reddit_items, x_items, raw_openai, raw_xai, raw_reddit_enriched return reddit_items, x_items, raw_openai, raw_xai, raw_reddit_enriched, reddit_error, x_error
def main(): def main():
@@ -227,7 +241,7 @@ def main():
mode = "x-only" mode = "x-only"
# Run research # Run research
reddit_items, x_items, raw_openai, raw_xai, raw_reddit_enriched = run_research( reddit_items, x_items, raw_openai, raw_xai, raw_reddit_enriched, reddit_error, x_error = run_research(
args.topic, args.topic,
sources, sources,
config, config,
@@ -265,6 +279,8 @@ def main():
) )
report.reddit = deduped_reddit report.reddit = deduped_reddit
report.x = deduped_x report.x = deduped_x
report.reddit_error = reddit_error
report.x_error = x_error
# Generate context snippet # Generate context snippet
report.context_snippet_md = render.render_context_snippet(report) report.context_snippet_md = render.render_context_snippet(report)
+8
View File
@@ -101,6 +101,13 @@ def parse_reddit_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
""" """
items = [] items = []
# Check for API errors first
if "error" in response and response["error"]:
error = response["error"]
err_msg = error.get("message", str(error)) if isinstance(error, dict) else str(error)
print(f"[REDDIT ERROR] OpenAI API error: {err_msg}", flush=True)
return items
# Try to find the output text # Try to find the output text
output_text = "" output_text = ""
if "output" in response: if "output" in response:
@@ -131,6 +138,7 @@ def parse_reddit_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
break break
if not output_text: if not output_text:
print(f"[REDDIT WARNING] No output text found in OpenAI response. Keys present: {list(response.keys())}", flush=True)
return items return items
# Extract JSON from the response # Extract JSON from the response
+22 -2
View File
@@ -46,7 +46,17 @@ def render_compact(report: schema.Report, limit: int = 15) -> str:
lines.append("") lines.append("")
# Reddit items # Reddit items
if report.reddit: if report.reddit_error:
lines.append("### Reddit Threads")
lines.append("")
lines.append(f"**ERROR:** {report.reddit_error}")
lines.append("")
elif report.mode in ("both", "reddit-only") and not report.reddit:
lines.append("### Reddit Threads")
lines.append("")
lines.append("*No relevant Reddit threads found for this topic.*")
lines.append("")
elif report.reddit:
lines.append("### Reddit Threads") lines.append("### Reddit Threads")
lines.append("") lines.append("")
for item in report.reddit[:limit]: for item in report.reddit[:limit]:
@@ -78,7 +88,17 @@ def render_compact(report: schema.Report, limit: int = 15) -> str:
lines.append("") lines.append("")
# X items # X items
if report.x: if report.x_error:
lines.append("### X Posts")
lines.append("")
lines.append(f"**ERROR:** {report.x_error}")
lines.append("")
elif report.mode in ("both", "x-only") and not report.x:
lines.append("### X Posts")
lines.append("")
lines.append("*No relevant X posts found for this topic.*")
lines.append("")
elif report.x:
lines.append("### X Posts") lines.append("### X Posts")
lines.append("") lines.append("")
for item in report.x[:limit]: for item in report.x[:limit]:
+9 -1
View File
@@ -153,9 +153,12 @@ class Report:
best_practices: List[str] = field(default_factory=list) best_practices: List[str] = field(default_factory=list)
prompt_pack: List[str] = field(default_factory=list) prompt_pack: List[str] = field(default_factory=list)
context_snippet_md: str = "" context_snippet_md: str = ""
# Status tracking
reddit_error: Optional[str] = None
x_error: Optional[str] = None
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
return { d = {
'topic': self.topic, 'topic': self.topic,
'range': { 'range': {
'from': self.range_from, 'from': self.range_from,
@@ -171,6 +174,11 @@ class Report:
'prompt_pack': self.prompt_pack, 'prompt_pack': self.prompt_pack,
'context_snippet_md': self.context_snippet_md, 'context_snippet_md': self.context_snippet_md,
} }
if self.reddit_error:
d['reddit_error'] = self.reddit_error
if self.x_error:
d['x_error'] = self.x_error
return d
def create_report( def create_report(