You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: the routine cannot archive, so the cleanup moves to interactive sessions
Field evidence from the real app, twice on 2026-07-30: archive_session from a
scheduled task run always raises "This tool requires explicit approval
regardless of permission mode" - the run's own session and ordinary ones
alike - and no permission rule bypasses it, while set_session_title
auto-approves in the same runs. The linkage-based cleanup step added in
d7cd6d9 was correct logic in a place it can never run unattended.
- SIDEBAR_TASK_PROMPT (and the SKILL.md copy) drop the cleanup step: the
routine does sync-plan, the renames, sweep-done, and nothing else. The
closing line now forbids archiving alongside deleting, and carries the
approval reason so the step does not get restored by the next reader.
- The cleanup ships instead as its own section of the skill, and a sentence
in the paste block: the user asks an interactive session to archive prior
runs, matched by scheduled-task linkage through get_session, never by title,
never the current session. list_sessions/get_session/archive_session stay
pre-approved, which is what makes that promptless.
- SIDEBAR_TASK_SIGNATURE is now the sentence opening rather than the whole
first line. A routine created before today carries its original prompt text
frozen in the app's task registry, and matching the opening keeps those runs
recognized through this reword.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CLAUDE.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,7 +21,8 @@ Zero-dependency Node CLI + Claude Code Stop hook that auto-titles Claude Code se
21
21
-**Titles live in two places.** Transcript `custom-title` JSONL records drive the CLI (resume picker, `list`); the desktop app's sidebar reads its own registry (one JSON file per session under `~/Library/Application Support/Claude/claude-code-sessions/`, macOS). Appending transcript records never updates the sidebar; the only external write path to the sidebar is the app's session-rename API, available to agents inside a session (`sync-plan` computes the diff for such an agent to apply).
22
22
-**The app writes its own auto-titles as `custom-title` records** - identical record type to a human rename, re-asserted every ~14 lines on active sessions. Record type says nothing about authorship. The ONLY authorship signal is `titleSource: 'user' | 'auto'` in the app-store files.
23
23
-**Active sessions get the app's REGISTRY title re-asserted into the transcript** (observed live 2026-07-29): the app's auto-titler writes its own record after ours and keeps re-asserting it, so our appended title is displaced, both sides end up agreeing on the app's name, and the plain diff goes empty. `sync-plan` detects that case - state `written` non-empty, transcript title not ours, registry `auto` and holding the displacing title - and re-proposes our newest title for the rename API to push. `appstore.isDisplaced` is that test, shared with the worker's mid-generate guard so the two cannot drift: a title arriving mid-call that the registry holds and marks `auto` is the app displacing us, not a rename, and the worker writes over it instead of abandoning the call. An earlier build was observed adopting the newest transcript record instead, so this is version-dependent; keep the mechanism best-effort.
24
-
-**Hooks fire in headless (`claude -p`) sessions too.** The worker's own title calls would recurse; `CLAUDE_SESSION_NAMER_WORKER=1` is the guard. Those headless calls also persist transcripts into the tmpdir project dir - the sweep excludes that dir and skips sessions whose first user text matches one of `titler.OUR_PROMPT_SIGNATURES`. That list is the two title-call signatures plus the sidebar routine's task-prompt opening line, and `SIDEBAR_TASK_PROMPT` is built from that constant, so the routine's own scheduled runs are never titled - a rename of those sessions is what broke the routine's cleanup step in the field. `titler.isOurOwnPrompt` is the shared test: worker, backfill, and sweep-done all use it.
24
+
-**Hooks fire in headless (`claude -p`) sessions too.** The worker's own title calls would recurse; `CLAUDE_SESSION_NAMER_WORKER=1` is the guard. Those headless calls also persist transcripts into the tmpdir project dir - the sweep excludes that dir and skips sessions whose first user text matches one of `titler.OUR_PROMPT_SIGNATURES`. That list is the two title-call signatures plus the sidebar routine's task-prompt opening line, and `SIDEBAR_TASK_PROMPT` is built from that constant, so the routine's own scheduled runs are never titled. `titler.isOurOwnPrompt` is the shared test: worker, backfill, and sweep-done all use it.
25
+
-**A scheduled task run cannot archive anything.**`archive_session` from a scheduled run always raises "This tool requires explicit approval regardless of permission mode" - its own session or any other, no permission rule bypasses it - while `set_session_title` auto-approves in the same run (observed live twice, 2026-07-30). So the sidebar routine renames and sweeps only; clearing its accumulated run sessions is something the user asks an interactive session to do, matched by scheduled-task linkage via `get_session` and never by title (this tool renames those sessions itself, which is what broke the title-matching version).
Copy file name to clipboardExpand all lines: docs/design.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,8 +54,9 @@ Setting that up is offered at onboarding rather than left in the README. On a TT
54
54
-**We never write the app's task registry.** It lives under `~/.claude/scheduled-tasks/<id>/`, and the app pairs it with state we cannot supply: the schedule, the working folder, and the tool permissions the user approves for the run. So both paths hand over instructions and let the app create its own routine, with the user's consent - the plugin skill for a plugin install, and a paste block from `install` or `sidebar-setup` for an npm one. Both come from one constant in `src/commands.js`, and a test asserts the skill file carries it verbatim.
55
55
-**The task prompt invokes the CLI by bare name.**`${CLAUDE_PLUGIN_ROOT}` resolves only in plugin components - hook and monitor commands, MCP and LSP config, and the plugin's own skill and agent content. A scheduled task's prompt is none of those: it lives in the app's task store, so the placeholder would reach the Bash tool as an unset shell variable and run `node "/bin/cli.js"`. The bare name covers both installs instead. npm puts it on the shell PATH; an enabled plugin's `bin/` is on the Bash tool's PATH in any session, and a scheduled run is an ordinary local session, so the plugin wrapper answers there. The prompt says to stop and report rather than guess at a path when the command is missing, which is what a task whose folder has the plugin disabled looks like.
56
56
- The routine never passes `--all`. Sessions the user renamed in the app stay excluded, and pushing them is a dead end anyway - the app's rename API keeps the user's title and answers success-shaped.
57
-
-**The cleanup step identifies prior runs by their scheduled-task linkage, never by title.** It matched titles until a live machine showed why that cannot work: a run session is an ordinary session, so this tool's own Stop hook titled it within a couple of replies, the exact-title match stopped landing, and the run sessions piled up - 8 counted in one day. The prompt now has the run list the app's sessions, read each candidate's scheduled-task linkage through the session-detail tool (`get_session`), and archive only the sessions linked to this task's own id. Never the current session, and never a session whose linkage could not be read. The permissions the setup offers to pre-approve cover those two extra tools.
58
-
-**The routine's own run sessions are never titled.** Their first user message is the task prompt itself, which makes them recognizable the same way the transcripts of our own headless calls are: `titler.SIDEBAR_TASK_SIGNATURE` is the prompt's literal opening line, and the template is built from that constant so the two cannot drift. A session opening with it gets no title, no drift check, and no done marker, on the hook path and in both sweeps (see Titling decisions). That saves a model call per scheduled run, and it is what keeps the linkage cleanup above from being undone by a rename a couple of replies later.
57
+
-**The routine archives nothing, and clearing its run sessions is an interactive job.** Every run leaves a session behind, and the routine looked like the obvious place to tidy them. It cannot be: an `archive_session` call from a scheduled run always raises "This tool requires explicit approval regardless of permission mode", for the run's own session and for ordinary ones alike, and no permission rule bypasses it, while `set_session_title` auto-approves in those same runs (observed live twice, 2026-07-30). A cleanup step in the task would stall on a prompt nobody is watching, so the task prompt asks for no archiving at all and says why, which is the part that keeps it from being re-added.
58
+
- The cleanup itself ships in the skill and the paste block, as something the user asks any interactive desktop session to do, where the pre-approved rules make it promptless. **Prior runs are identified by their scheduled-task linkage, never by title.** Title matching is what the first version did, and a live machine showed why it cannot work: a run session is an ordinary session, so this tool's own Stop hook titled it within a couple of replies, the exact-title match stopped landing, and the run sessions piled up - 8 counted in one day. The rule is instead: list the app's sessions, read each candidate's linkage through the session-detail tool (`get_session`), archive only the ones linked to this task's id, never the current session, and never a session whose linkage could not be read. `list_sessions`, `get_session` and `archive_session` stay in the pre-approved set for exactly this.
59
+
-**The routine's own run sessions are never titled.** Their first user message is the task prompt itself, which makes them recognizable the same way the transcripts of our own headless calls are: `titler.SIDEBAR_TASK_SIGNATURE` is how the prompt opens, and the template is built from that constant so the two cannot drift. It is the sentence opening rather than the whole first line, because a routine created weeks ago carries the prompt text it was created with, frozen in the app's task registry, and only the tail of that line has ever been reworded. A session opening with it gets no title, no drift check, and no done marker, on the hook path and in both sweeps (see Titling decisions). That saves a model call per scheduled run, and it leaves the run sessions uniformly named until somebody clears them, rather than scattered under titles this tool invented for its own automation.
Copy file name to clipboardExpand all lines: skills/setup-sidebar-sync/SKILL.md
+14-4Lines changed: 14 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -40,9 +40,8 @@ Sync claude-session-namer titles into the Claude Code desktop sidebar, then tidy
40
40
2. The output is JSON lines, each {"sessionId", "currentTitle", "newTitle"}. Empty output means nothing to sync - continue with step 4.
41
41
3. For each line in order, call the app's session-rename tool (set_session_title) with that sessionId and newTitle. Stop immediately on the first error - no retries, no continuing - and report what failed.
42
42
4. Run `claude-session-namer sweep-done`. It is a no-op unless the user turned done markers on.
43
-
5. Cleanup: archive prior run sessions of this same scheduled task, identified by their scheduled-task linkage and nothing else. List this app's sessions, and for each candidate call the app's session-detail tool (get_session), which reports the scheduled task a session belongs to. Archive a session only when that linkage names this task's id, `session-title-sidebar-sync`. Never match on title: claude-session-namer renames these run sessions itself, usually within a couple of replies, so a run's title says nothing about which task produced it. Never archive the current session, and never archive a session whose linkage you could not read.
44
43
45
-
Take no actions beyond these: sync-plan, sweep-done, the per-session rename calls, the session lookups step 5 needs, and archiving prior run sessions of this task. Never run backfill or any other command, and never delete anything.
44
+
Take no actions beyond these: sync-plan, sweep-done, and the per-session rename calls. Never run backfill or any other command, and never archive or delete anything. Tidying up the sessions these runs leave behind is deliberately not part of this routine: the app requires manual approval for archive_session in a scheduled run whatever permission rules are set, so a run that tried would sit waiting for a person who is not there. That cleanup belongs to an interactive session.
46
45
```
47
46
48
47
The task's working folder needs claude-session-namer reachable from a Bash call: any folder for an npm install, and a folder where this plugin is enabled otherwise.
@@ -60,13 +59,24 @@ Run-time prompt approvals do not reliably persist for the app's own tools, so of
60
59
"mcp__ccd_session_mgmt__archive_session"
61
60
```
62
61
63
-
Say what each one is for in a line: the two commands the routine runs, the app's rename tool that pushes titles, and the three the cleanup step uses to find its own prior runs and archive them. If the user declines, that is fine - the routine still works, it just pauses on a prompt whenever an approval has not stuck.
62
+
Say what each one is for in a line: the two commands the routine runs, the app's rename tool that pushes titles, and the three the user's own cleanup uses to find prior runs and archive them (section 7). If the user declines, that is fine - the routine still works, it just pauses on a prompt whenever an approval has not stuck.
64
63
65
64
## 6. Say what to expect
66
65
67
66
Keep it to a few lines:
68
67
69
68
- The task runs only while the desktop app is open and the machine is awake. A missed hour is skipped, and the next run picks up everything outstanding anyway.
70
-
- Offer to prove the path once now, whether or not the rules were added, by running the task's steps yourself in this session: `claude-session-namer sync-plan`, then the `set_session_title` call for each line it prints, then `claude-session-namer sweep-done`. Skip step 5, the cleanup - there is nothing to tidy yet.
69
+
- Offer to prove the path once now, whether or not the rules were added, by running the task's steps yourself in this session: `claude-session-namer sync-plan`, then the `set_session_title` call for each line it prints, then `claude-session-namer sweep-done`.
71
70
- Never test it by scheduling the task to fire. A one-time `fireAt` run clears the cron schedule and the task disables itself once it has fired, so the hourly sync would be dead from that moment with nothing said.
72
71
- Sessions the user renamed in the app are left out of every sync. The app refuses an agent rename of those, so pushing them is not possible and not attempted.
72
+
- Each run leaves a session behind, and the routine cannot clear them itself. Point at section 7 in one line.
73
+
74
+
## 7. Clearing old run sessions
75
+
76
+
The routine archives nothing, by design. An `archive_session` call from a scheduled run always raises a manual approval - "This tool requires explicit approval regardless of permission mode" - and no permission rule bypasses it (observed live 2026-07-30, twice: for the run's own session and for ordinary sessions alike, while `set_session_title` auto-approved in those same runs). A cleanup step inside the task would stall waiting for a person who is not watching.
77
+
78
+
So it is something the user asks for, in any interactive desktop session, where the rules from section 5 make it promptless. Tell them that, and tell them what the session doing it has to do:
79
+
80
+
- Find prior runs by their **scheduled-task linkage**: list the app's sessions, call `get_session` on each candidate, and act only on the ones whose linkage names `session-title-sidebar-sync`.
81
+
-**Never match on title.** claude-session-namer renames these run sessions itself, usually within a couple of replies, so a run's title says nothing about which task produced it.
82
+
- Never archive the current session, and never archive a session whose linkage could not be read.
0 commit comments