feat(release): v2.9.4 - move save into Python script, zero post-invitation noise
Add --save-dir flag to last30days.py that saves raw research output during the existing script run. Remove entire "Save Research to Documents" section from SKILL.md (~45 lines). No more extra tool calls, no (No output), no multi-minute cogitation after invitation. Tested: --mock confirms file creation and duplicate date suffixing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [2.9.4] - 2026-03-06
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Move save into Python script via `--save-dir` flag - raw research data saved during the existing script Bash call, zero extra tool calls after invitation
|
||||||
|
- Remove entire "Save Research to Documents" section from SKILL.md (~45 lines removed)
|
||||||
|
- No more `📎` footer, no Bash heredoc, no `(No output)`, no multi-minute cogitation after research
|
||||||
|
|
||||||
## [2.9.3] - 2026-03-06
|
## [2.9.3] - 2026-03-06
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ metadata:
|
|||||||
- prompts
|
- prompts
|
||||||
---
|
---
|
||||||
|
|
||||||
# last30days v2.9.3: Research Any Topic from the Last 30 Days
|
# last30days v2.9.4: Research Any Topic from the Last 30 Days
|
||||||
|
|
||||||
Research ANY topic across Reddit, X, YouTube, TikTok, Hacker News, Polymarket, and the web. Surface what people are actually discussing, recommending, betting on, and debating right now.
|
Research ANY topic across Reddit, X, YouTube, TikTok, Hacker News, Polymarket, and the web. Surface what people are actually discussing, recommending, betting on, and debating right now.
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ If `--agent` appears in ARGUMENTS (e.g., `/last30days plaud granola --agent`):
|
|||||||
5. **Skip** the follow-up invitation ("I'm now an expert on X...")
|
5. **Skip** the follow-up invitation ("I'm now an expert on X...")
|
||||||
6. **Output** the complete research report and stop - do not wait for further input
|
6. **Output** the complete research report and stop - do not wait for further input
|
||||||
|
|
||||||
Agent mode still saves the research briefing to `~/Documents/Last30Days/` using the same logic as interactive mode (see "Save Research to Documents" section).
|
Agent mode saves raw research data to `~/Documents/Last30Days/` automatically via `--save-dir` (handled by the script, no extra tool calls).
|
||||||
|
|
||||||
Agent mode report format:
|
Agent mode report format:
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ if [ -z "${SKILL_ROOT:-}" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 "${SKILL_ROOT}/scripts/last30days.py" "$ARGUMENTS" --emit=compact --no-native-web # Add --x-handle=HANDLE if RESOLVED_HANDLE is set
|
python3 "${SKILL_ROOT}/scripts/last30days.py" "$ARGUMENTS" --emit=compact --no-native-web --save-dir=~/Documents/Last30Days # Add --x-handle=HANDLE if RESOLVED_HANDLE is set
|
||||||
```
|
```
|
||||||
|
|
||||||
Use a **timeout of 300000** (5 minutes) on the Bash call. The script typically takes 1-3 minutes.
|
Use a **timeout of 300000** (5 minutes) on the Bash call. The script typically takes 1-3 minutes.
|
||||||
@@ -498,58 +498,9 @@ For `/last30days war in Iran` (NEWS):
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Save Research to Documents
|
|
||||||
|
|
||||||
After displaying the invitation, save the complete research briefing to `~/Documents/Last30Days/`. This happens automatically on every run.
|
|
||||||
|
|
||||||
**Generate the filename** from TOPIC:
|
|
||||||
- Lowercase, replace spaces/special chars with hyphens, remove consecutive hyphens, trim to 60 chars
|
|
||||||
- Example: "Claude Code Best Practices" → `claude-code-best-practices.md`
|
|
||||||
- If file already exists, append today's date: `{slug}-YYYY-MM-DD.md`
|
|
||||||
|
|
||||||
**End your invitation with a single `📎` footer line:**
|
|
||||||
|
|
||||||
```
|
|
||||||
📎 ~/Documents/Last30Days/{slug}.md
|
|
||||||
```
|
|
||||||
|
|
||||||
**Then save using a foreground Bash command** (cat is instant - no delay):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/Documents/Last30Days && cat > ~/Documents/Last30Days/{slug}.md << 'RESEARCH_EOF'
|
|
||||||
# {TOPIC}
|
|
||||||
|
|
||||||
> Researched {date} | Query type: {QUERY_TYPE} | Target tool: {TARGET_TOOL or "general"}
|
|
||||||
|
|
||||||
## What I learned
|
|
||||||
|
|
||||||
{The full synthesis section you just displayed - all topics, patterns, and citations}
|
|
||||||
|
|
||||||
## Stats
|
|
||||||
|
|
||||||
{The full stats box with source counts and engagement - copy exactly as displayed}
|
|
||||||
|
|
||||||
## Follow-up suggestions
|
|
||||||
|
|
||||||
{The 2-3 specific suggestions from the invitation block}
|
|
||||||
|
|
||||||
---
|
|
||||||
*Generated by [last30days](https://github.com/mvanhorn/last30days-skill) v2.9.3*
|
|
||||||
RESEARCH_EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
**CRITICAL RULES:**
|
|
||||||
1. NEVER use the `Write` tool - it displays "Wrote N lines..." which clutters the output
|
|
||||||
2. NEVER use `run_in_background: true` - background callbacks cause the model to re-engage, hallucinate fake user messages, and generate unsolicited responses
|
|
||||||
3. Use FOREGROUND Bash with `cat >` - it executes instantly (sub-second) and produces no output
|
|
||||||
4. The `📎` line is part of your text message, not a separate tool call
|
|
||||||
5. Do NOT output any text after the save Bash call. No "saved", no "ready for questions", nothing.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## WAIT FOR USER'S RESPONSE
|
## WAIT FOR USER'S RESPONSE
|
||||||
|
|
||||||
**STOP and wait** for the user to respond. Do NOT generate any text after the save.
|
**STOP and wait** for the user to respond. Do NOT call any tools after displaying the invitation. The research script already saved raw data to `~/Documents/Last30Days/` via `--save-dir`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
title: "fix: Eliminate save-related output noise after research"
|
||||||
|
type: fix
|
||||||
|
status: active
|
||||||
|
date: 2026-03-06
|
||||||
|
---
|
||||||
|
|
||||||
|
# fix: Eliminate save-related output noise after research
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
|
||||||
|
After research completes, the auto-save feature adds 3-5 lines of unwanted output below the clean invitation block. Every approach tried so far has made things worse:
|
||||||
|
|
||||||
|
| Version | Approach | Lines added | Side effects |
|
||||||
|
|---------|----------|-------------|--------------|
|
||||||
|
| v2.9.1 | `Write` tool | ~5 | Shows "Wrote 43 lines to..." |
|
||||||
|
| v2.9.2 | `run_in_background: true` | ~5 | Background callback triggers 2-4 min cogitation, model hallucinates fake "Human:" messages and generates unsolicited multi-paragraph responses |
|
||||||
|
| v2.9.3 | Foreground `cat >` heredoc | ~3 | Shows `(No output)`, still triggers 2-4 min cogitation |
|
||||||
|
|
||||||
|
**Current v2.9.3 noise (3 lines):**
|
||||||
|
```
|
||||||
|
⏺ Bash(mkdir -p ~/Documents/Last30Days && cat > ...)
|
||||||
|
⎿ (No output)
|
||||||
|
✻ Churned for 2m 38s
|
||||||
|
```
|
||||||
|
|
||||||
|
The root cause is that ANY tool call after the invitation creates unavoidable Claude Code UI chrome, and the model cogitates for minutes regardless of foreground vs background.
|
||||||
|
|
||||||
|
## Proposed Solutions
|
||||||
|
|
||||||
|
### Option A: Remove auto-save entirely (Recommended)
|
||||||
|
|
||||||
|
Remove the entire "Save Research to Documents" section from SKILL.md. The research lives in the conversation. Zero extra tool calls, zero extra lines, zero cogitation.
|
||||||
|
|
||||||
|
**Changes to `SKILL.md`:**
|
||||||
|
- Delete the "Save Research to Documents" section (~45 lines)
|
||||||
|
- Update agent mode reference (line 126) - remove save mention
|
||||||
|
- Update security section (line 612) - change "Saves research briefings" to past tense or conditional
|
||||||
|
- Remove `📎` footer line from invitation format
|
||||||
|
- Simplify "WAIT FOR USER'S RESPONSE" section
|
||||||
|
|
||||||
|
**What users lose:** Auto-saved .md files in `~/Documents/Last30Days/`
|
||||||
|
**What users gain:** Clean output ending exactly at the invitation block
|
||||||
|
|
||||||
|
### Option B: Move save into Python script
|
||||||
|
|
||||||
|
Add `--save-dir` flag to `last30days.py`. The script saves its raw output during its existing Bash call (which already runs). Zero extra tool calls.
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- `scripts/last30days.py` - add `--save-dir` argument, write raw output to file at end of execution
|
||||||
|
- `SKILL.md` - add `--save-dir=~/Documents/Last30Days` to the script invocation, remove save section
|
||||||
|
|
||||||
|
**What users lose:** Saved file contains raw research data, not Claude's synthesis
|
||||||
|
**What users gain:** Zero extra lines, data is still preserved
|
||||||
|
|
||||||
|
### Option C: Opt-in save via follow-up command
|
||||||
|
|
||||||
|
Remove auto-save. Add a note to the invitation: "Say 'save' to save this research." When user says "save", run the Bash heredoc then.
|
||||||
|
|
||||||
|
**Changes to `SKILL.md`:**
|
||||||
|
- Delete auto-save section
|
||||||
|
- Add "save" as a recognized intent in "WHEN USER RESPONDS" section
|
||||||
|
- Save logic only runs when explicitly requested
|
||||||
|
|
||||||
|
**What users lose:** Nothing - save is still available on demand
|
||||||
|
**What users gain:** Clean output by default, save when they want it
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] Research output ends at the invitation block with zero tool calls after it
|
||||||
|
- [ ] No `(No output)` line visible after invitation
|
||||||
|
- [ ] No multi-minute cogitation after invitation
|
||||||
|
- [ ] No hallucinated fake user messages
|
||||||
|
- [ ] Version bumped to v2.9.4
|
||||||
|
- [ ] CHANGELOG updated
|
||||||
|
- [ ] Synced to ~/.claude, ~/.agents, ~/.codex via sync.sh
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- Source: `/Users/mvanhorn/last30days-skill-private/SKILL.md`
|
||||||
|
- Deployed to: `~/.claude/skills/last30days/SKILL.md`
|
||||||
|
- Sync command: `bash scripts/sync.sh`
|
||||||
|
- Current version: v2.9.3
|
||||||
@@ -1250,6 +1250,13 @@ def main():
|
|||||||
default=False,
|
default=False,
|
||||||
help="Skip native web search backends (Parallel/Brave/OpenRouter). Use when the assistant has its own WebSearch tool.",
|
help="Skip native web search backends (Parallel/Brave/OpenRouter). Use when the assistant has its own WebSearch tool.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--save-dir",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
metavar="DIR",
|
||||||
|
help="Auto-save raw research output to DIR/{topic-slug}.md",
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
args.topic = " ".join(args.topic) if args.topic else None
|
args.topic = " ".join(args.topic) if args.topic else None
|
||||||
@@ -1604,6 +1611,20 @@ def main():
|
|||||||
# Output result
|
# Output result
|
||||||
output_result(report, args.emit, web_needed, args.topic, from_date, to_date, missing_keys, args.days, source_info)
|
output_result(report, args.emit, web_needed, args.topic, from_date, to_date, missing_keys, args.days, source_info)
|
||||||
|
|
||||||
|
# Auto-save raw research to file if --save-dir is set
|
||||||
|
if args.save_dir:
|
||||||
|
import re
|
||||||
|
save_dir = Path(args.save_dir).expanduser()
|
||||||
|
save_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
slug = re.sub(r'[^a-z0-9]+', '-', args.topic.lower()).strip('-')[:60]
|
||||||
|
save_path = save_dir / f"{slug}.md"
|
||||||
|
if save_path.exists():
|
||||||
|
save_path = save_dir / f"{slug}-{datetime.now().strftime('%Y-%m-%d')}.md"
|
||||||
|
content = render.render_compact(report, missing_keys=missing_keys)
|
||||||
|
content += "\n" + render.render_source_status(report, source_info)
|
||||||
|
save_path.write_text(content, encoding="utf-8")
|
||||||
|
print(f"📎 {save_path}", file=sys.stderr)
|
||||||
|
|
||||||
# Persist findings to SQLite if requested
|
# Persist findings to SQLite if requested
|
||||||
if args.store:
|
if args.store:
|
||||||
import store as store_mod
|
import store as store_mod
|
||||||
|
|||||||
Reference in New Issue
Block a user