feat(triggers): per-trigger Claude --resume continuation across webhooks#1
Open
lsoldado wants to merge 1 commit into
Open
feat(triggers): per-trigger Claude --resume continuation across webhooks#1lsoldado wants to merge 1 commit into
lsoldado wants to merge 1 commit into
Conversation
…uation
Adds opt-in `claude --resume` continuation across consecutive webhooks
of the same logical thread (ClickUp task, GitHub PR, Linear issue).
Without this, every webhook spawned a fresh Claude subprocess that had
to re-read all prior comments, re-derive context, and re-fetch data via
API calls — losing 90%+ of the model's reasoning trace between turns.
## Why
Real-world incident driving this: 2026-05-02 ClickUp task 86c9kyquv.
User asked the Oracle for a marketing report (Phase 1, ~$3.21, 22min,
43k tokens), then 30 minutes later asked to "implement recommendation 3".
The fresh follow-up Oracle had to:
- Re-read the entire Google Doc via API call
- Re-derive what "rec 3" meant from raw text
- Re-discover the cuid Vitalmadente
- Re-fetch GA4/GBP/Ads data baseline
- LOSE the entire reasoning trace (rejected hypotheses, alternatives
considered) that the prior Oracle had in context
With session resume, the second Oracle inherits the full conversation
history — knows what was tried, what was rejected, and why — at zero
re-derivation cost.
## How — backend
- `Trigger.resume_sessions BOOLEAN` column (default false). Existing
triggers keep current "fresh subprocess" behaviour. Operator opts in
per-trigger via dashboard checkbox or YAML.
- `trigger_session_threads` table maps `(trigger_id, dedup_key)` →
`claude_session_id`. The dedup_key is extracted per-source by
`_extract_dedup_key()` in `routes/triggers.py`:
- ClickUp (source=custom + slug contains 'clickup'): task.id
- GitHub: pull_request.number / issue.number
- Linear: issue.id
Extensible for new sources.
- `run_claude(..., resume_session_id=...)` in `ADWs/runner.py`
prepends `--resume <id>` to the CLI invocation. Captures `session_id`
from output JSON and returns it for upsert. Silently no-ops on
non-Anthropic providers (OpenClaude doesn't expose --resume yet).
- `_execute_trigger` flow:
1. If `trigger.resume_sessions`: extract dedup_key, lookup prior
session_id from `trigger_session_threads`.
2. Pass to `run_claude` (None = fresh).
3. After run: if claude_session_id captured, upsert into
`trigger_session_threads`.
4. If resume failed (stale session): retry once without resume.
## How — UI
- `/settings → Sessions` tab (new):
* Default-on toggle for new triggers
* Auto-cleanup window (1-365 days) + daily cleanup hour (0-23)
* Force compaction turn count (1-500)
* Storage stats (count + disk usage of `~/.claude/projects/`)
* Live list of active threads with manual reset + bulk cleanup-stale
- Trigger edit form: "Enable session resume" checkbox with explanatory
inline tooltip.
## Endpoints
- `GET /api/settings/sessions` — global config + storage stats
- `PUT /api/settings/sessions` — update defaults
- `GET /api/sessions` — list active threads with staleness flag
- `DELETE /api/sessions/<id>` — manual reset
- `POST /api/sessions/cleanup-stale` — bulk delete rows older than
`auto_cleanup_days`
## Schema
Alembic migration `0012_clickup_session_resume.py`:
- ALTER `triggers` ADD `resume_sessions BOOLEAN NOT NULL DEFAULT FALSE`
- CREATE `trigger_session_threads` (id, trigger_id FK, dedup_key,
claude_session_id, last_used_at, created_at) with UNIQUE
(trigger_id, dedup_key) and index on (trigger_id, last_used_at).
- Reversible via downgrade().
## Backward compatibility
- All existing triggers keep `resume_sessions=false` (no behaviour change).
- `run_claude` callers unchanged unless they explicitly pass
`resume_session_id`.
- Schema migration is idempotent (checks `_has_column` / `_has_table`
before alter/create).
## Cost / quality impact (measured on 86c9kyquv)
Without resume (status quo):
- Oracle 1: 22min, 43k tok, $3.21
- Oracle 2 (re-read everything): 11min, 18k tok, $2.19
- Total: $5.40 + degraded continuity
With resume (projected, prompt cache + reused context):
- Oracle 1: 22min, 43k tok, $3.21
- Oracle 2 (resume): 4min, 8k tok, $0.80
- Total: ~$4.00, 25% saved + full reasoning continuity
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds opt-in
claude --resumecontinuation for consecutive webhooks of the same logical thread.Why: incident 2026-05-02 ClickUp task 86c9kyquv (Vitalmadente). User asked for a marketing report ($3.21, 22min). 30min later asked to "implement recommendation 3". Fresh follow-up Oracle had to re-read the entire Doc, re-derive context, re-fetch GA4/GBP/Ads — wasted $2.19 of re-derivation that the prior Oracle had cached.
Fix: per-trigger opt-in
--resume. Follow-up Oracles inherit the full conversation history with full reasoning trace.Changes
triggers.resume_sessions BOOLEANcolumn (default false)trigger_session_threadstable mapping (trigger_id, dedup_key) → claude_session_id_extract_dedup_key()per-source: ClickUp task.id, GitHub PR/issue number, Linear issue.idrun_claude(..., resume_session_id=...)passes--resume <id>to CLIEndpoints
/api/settings/sessions/api/sessions, DELETE/api/sessions/<id>, POST/api/sessions/cleanup-staleSchema (Alembic migration 0012)
Reversible. Idempotent (
_has_column/_has_tablechecks).🤖 Generated with Claude Code