From d97592c5c634b198d2971db390b119fd3663d266 Mon Sep 17 00:00:00 2001
From: Ansu <230804140223@stu.nepu.edu.cn>
Date: Sun, 18 Oct 2026 11:22:11 +0800
Subject: [PATCH 1/2] fix(sleep): harvest pasted briefs instead of dropping
them as host noise
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A user message that arrives wrapped in angle brackets was classified as
system noise, so a brief pasted into the Claude Code desktop app
(`…`) never became a task:
the session was mined from a short follow-up instead, or dropped entirely
when the brief was its only user turn.
- unwrap `` and keep the inner text as the user's
- drop host-injected `` blocks from the intent rather
than copying them into it
- keep the "message is nothing but one host tag block" filter, but only
for a closed list of host tags (command / local-command / system-reminder
/ bash / IDE / Copilot `environment_info`), so a wrapper this list does
not know about loses its tags rather than the session's request
- treat a session whose user turns were all filtered as engine-generated
only when it has no assistant work
Fixes #318
---
skillopt_sleep/harvest.py | 69 +++++++++++++++++++--
tests/test_sleep_engine.py | 122 +++++++++++++++++++++++++++++++++++++
2 files changed, 186 insertions(+), 5 deletions(-)
diff --git a/skillopt_sleep/harvest.py b/skillopt_sleep/harvest.py
index 7f3bee8f7..8ecf6e2e4 100644
--- a/skillopt_sleep/harvest.py
+++ b/skillopt_sleep/harvest.py
@@ -17,6 +17,7 @@
import json
import os
+import re
from datetime import datetime
from typing import Any, Dict, Iterable, List, Optional
@@ -134,13 +135,56 @@ def _detect_feedback(text: str) -> List[str]:
return sig
+# ── Issue #318: host wrappers around user text ─────────────────────────
+#
+# The Claude Code desktop app delivers a pasted block as the user message,
+# wrapped in ``…``. The brief
+# the user pasted is typed by them and is often the whole point of the
+# session, so the wrapper is unwrapped instead of dropped.
+_PASTED_CONTENT_RE = re.compile(r"?pasted_content[^>]*>", re.IGNORECASE)
+
+# Host-injected status notes and environment blocks. A message that is only
+# a reminder is host noise, and a reminder prefixed to a real request must
+# not ride into the mined intent as text the user never wrote.
+_SYSTEM_REMINDER_RE = re.compile(
+ r".*?\s*", re.IGNORECASE | re.DOTALL
+)
+
+# Tags the host injects as a whole user message: expanded slash-command
+# bodies, local-command output, shell-mode input and editor selections. The
+# list is closed deliberately — a wrapper this list does not know about is
+# treated as user text, so such a message keeps its request instead of losing
+# it. is listed as well as stripped, so a variant carrying
+# attributes is still recognised as host noise.
+_HOST_META_TAGS = (
+ "command-name", "command-message", "command-args",
+ "local-command-stdout", "local-command-caveat",
+ "system-reminder",
+ "bash-input", "bash-stdout", "bash-stderr",
+ "ide_opened_file", "ide_selection",
+ # VS Code Copilot Chat brackets the machine context of every request
+ # (…); harvesting it leaks local
+ # environment detail and was never a user prompt.
+ "environment_info",
+)
+
+
+def _strip_host_wrappers(text: str) -> str:
+ """Unwrap pasted user content and drop host-injected blocks.
+
+ Only the two wrappers above are touched: a message keeps its text when it
+ is wrapped in a tag the host does not own.
+ """
+ if "<" not in text:
+ return text
+ return _PASTED_CONTENT_RE.sub("", _SYSTEM_REMINDER_RE.sub("", text))
+
+
def _is_meta_prompt(text: str) -> bool:
"""Skip slash-commands / system noise that aren't real user intents."""
- t = text.strip()
+ t = _strip_host_wrappers(text).strip()
if not t:
return True
- if t.startswith("<") and t.endswith(">"):
- return True
if t.startswith("/") and len(t.split()) <= 3:
return True
if t.startswith("[Pasted text") or t.startswith("Caveat:"):
@@ -152,6 +196,14 @@ def _is_meta_prompt(text: str) -> bool:
return True
if t.startswith("# /"):
return True
+ # A message that is nothing but one host tag block (``,
+ # ``, …) carries no user intent. The tag list is
+ # closed on purpose: text that merely arrives wrapped in angle brackets
+ # is user content, and dropping it loses the session's real request
+ # (issue #318: the desktop app delivers a pasted brief that way).
+ if t.startswith("<") and t.endswith(">"):
+ head = t[1:].split(">", 1)[0].split(None, 1)
+ return bool(head) and head[0].lower() in _HOST_META_TAGS
return False
@@ -215,7 +267,11 @@ def _is_headless_replay(digest: "SessionDigest") -> bool:
if digest.n_user_turns > 1:
return False
if digest.n_user_turns == 0:
- return True
+ # Every user turn was filtered as host noise or engine input. That is
+ # not a replay signal by itself: a session whose user turns were all
+ # host-wrapped still holds real assistant work (#318), so only a
+ # transcript with no work at all counts as engine-generated.
+ return digest.n_assistant_turns == 0
prompt = digest.user_prompts[0] if digest.user_prompts else ""
for marker in _REPLAY_PROMPT_MARKERS:
if marker in prompt:
@@ -278,7 +334,10 @@ def digest_transcript(path: str) -> Optional[SessionDigest]:
# other sessions, usage-limit notices. The user typed none of it.
# A body that names a known agent session is still kept, so
# _is_agent_session can drop the whole session as before.
- text = _text_from_content(content)
+ # Host wrappers are removed before the meta test: a pasted brief
+ # (#318) is user content, and a prefix must not
+ # ride into the intent (see _strip_host_wrappers).
+ text = _strip_host_wrappers(_text_from_content(content))
if rec.get("isMeta") is True and not any(
marker in text for marker in _AGENT_SESSION_MARKERS
):
diff --git a/tests/test_sleep_engine.py b/tests/test_sleep_engine.py
index 62fe982e7..91662ba52 100644
--- a/tests/test_sleep_engine.py
+++ b/tests/test_sleep_engine.py
@@ -82,6 +82,128 @@ def test_meta_prompt_filter(self):
self.assertTrue(_is_meta_prompt("x"))
self.assertFalse(_is_meta_prompt("please refactor the auth module"))
+ def _write_claude_session(self, path, turns):
+ """Write one Claude Code transcript; ``turns`` are (role, text) pairs.
+
+ Records are an hour apart so the sub-3-second heuristic in
+ _is_headless_replay never decides these fixtures.
+ """
+ self._write_jsonl(path, [
+ {"type": role,
+ "timestamp": f"2026-10-15T0{index + 1}:00:00Z",
+ "cwd": "/repo/example",
+ "sessionId": os.path.splitext(os.path.basename(path))[0],
+ "message": {"role": role, "content": text}}
+ for index, (role, text) in enumerate(turns)
+ ])
+
+ def test_meta_prompt_keeps_pasted_user_content(self):
+ # issue #318: the desktop app delivers a pasted brief inside a tag
+ # pair carrying the same id. The user typed that brief, so it is user
+ # content, and dropping it left the session's follow-up as the intent.
+ from skillopt_sleep.harvest import _strip_host_wrappers
+
+ brief = ('\n'
+ 'Install tool X into a dedicated folder, pin the version.\n'
+ '')
+
+ self.assertFalse(_is_meta_prompt(brief))
+ # the helper removes the tags only; callers strip the remainder
+ self.assertEqual(
+ _strip_host_wrappers(brief),
+ "\nInstall tool X into a dedicated folder, pin the version.\n",
+ )
+
+ def test_meta_prompt_filters_only_known_host_tags(self):
+ self.assertTrue(_is_meta_prompt("ok"))
+ self.assertTrue(_is_meta_prompt(
+ "skillopt-sleep\n"
+ "/skillopt-sleep"
+ ))
+ # VS Code Copilot Chat brackets each request's machine context the
+ # same way; it is host detail, not a prompt, and must stay out
+ # (tests/test_copilot_harvest.py pins the privacy boundary).
+ self.assertTrue(_is_meta_prompt(
+ "private machine context"
+ ))
+ # A wrapper the host does not own is user text, not noise: it must
+ # survive the filter with its content instead of being dropped for
+ # being wrapped in angle brackets, which is what lost pasted briefs.
+ self.assertFalse(_is_meta_prompt("Ship 1.4"))
+
+ def test_pasted_brief_becomes_the_mined_intent(self):
+ brief = ('\n'
+ 'Install tool X into a dedicated folder, pin the version, '
+ 'back up the configs first.\n'
+ '')
+ with tempfile.TemporaryDirectory() as tmp:
+ path = os.path.join(tmp, "pasted-session.jsonl")
+ self._write_claude_session(path, [
+ ("user", brief),
+ ("assistant", "Plan: ..."),
+ ("user", "Yes, go ahead with option A."),
+ ("assistant", "Done."),
+ ])
+ digest = digest_transcript(path)
+
+ [task] = heuristic_mine([digest])
+ self.assertEqual(
+ task.intent,
+ "Install tool X into a dedicated folder, pin the version, back up the configs first.",
+ )
+ self.assertNotIn("pasted_content", task.intent)
+ # the follow-up stays available as constraint context, not as intent
+ self.assertIn("Yes, go ahead with option A.", task.context_excerpt)
+
+ def test_session_whose_only_user_turn_is_a_pasted_brief_survives_harvest(self):
+ from skillopt_sleep.harvest import harvest
+
+ brief = ('\nInstall tool X into a dedicated folder.\n'
+ '')
+ with tempfile.TemporaryDirectory() as tmp:
+ project_dir = os.path.join(tmp, "projects", "-repo-example")
+ os.makedirs(project_dir)
+ self._write_claude_session(os.path.join(project_dir, "pasted-only.jsonl"), [
+ ("user", brief),
+ ("assistant", "Plan: ..."),
+ ("assistant", "Done."),
+ ])
+
+ digests = harvest(os.path.join(tmp, "projects"), scope="all")
+
+ self.assertEqual([d.session_id for d in digests], ["pasted-only"])
+ self.assertEqual(digests[0].user_prompts,
+ ["Install tool X into a dedicated folder."])
+
+ def test_system_reminder_prefix_does_not_ride_into_the_intent(self):
+ request = "Yes: run the third review on range r2..abc1234 and update the report."
+ with tempfile.TemporaryDirectory() as tmp:
+ path = os.path.join(tmp, "reminder-prefix.jsonl")
+ self._write_claude_session(path, [
+ ("user", "\nStatus note injected by the host app.\n"
+ f"\n\n{request}"),
+ ("assistant", "Running the review."),
+ ])
+ digest = digest_transcript(path)
+
+ self.assertEqual(digest.user_prompts, [request])
+ [task] = heuristic_mine([digest])
+ self.assertEqual(task.intent, request)
+ self.assertNotIn("Status note", task.intent)
+
+ def test_session_without_user_turns_is_a_replay_only_when_it_has_no_work(self):
+ from skillopt_sleep.harvest import _is_headless_replay
+
+ # issue #318: all user turns filtered out is not a replay signal on
+ # its own — real assistant work means the session is a real one.
+ worked = SessionDigest(session_id="s", project="/repo/example",
+ assistant_finals=["Done."],
+ n_user_turns=0, n_assistant_turns=2)
+ empty = SessionDigest(session_id="s", project="/repo/example")
+
+ self.assertFalse(_is_headless_replay(worked))
+ self.assertTrue(_is_headless_replay(empty))
+
def _claude_session_with_injected_messages(self, tmp, follow_up):
"""A Claude Code session as written to disk when a skill is loaded.
From f5f1f9a15510087da199b06cf294e899e8bc0cb2 Mon Sep 17 00:00:00 2001
From: Ansu <230804140223@stu.nepu.edu.cn>
Date: Mon, 19 Oct 2026 13:06:24 +0800
Subject: [PATCH 2/2] fix(sleep): strip only the host's leading notes, keep
no-intent records out
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Two boundary corrections from review.
* Host stripping no longer rewrites text the user wrote. `_strip_host_wrappers`
removed every `…` block in the message, so
a prompt that quoted one — inline code, a fenced XML example — lost it, and
both the harvested request and its mined intent changed. Removal is now
anchored: the block is dropped only while it leads the message, which is the
form the host prepends. A quoted sample anywhere after the user's own text
survives verbatim. The tag match now also tolerates attributes.
* A transcript with no user turns is a replay again, whatever assistant work it
holds. The zero-user/assistant-present exception admitted host-only isMeta
records before the session cap; such a record mines nothing, so under
`--max-sessions` it displaced a real session and left the night with no
tasks. Reverted to the base behaviour. The pasted-brief fix does not need the
exception: unwrapping now yields a user turn, which is what the new tests
assert.
Measured with the new tests against the pre-review head `d97592c`: 3 failed,
136 passed, 1 skipped —
test_inline_code_system_reminder_example_is_user_text
['How ... escape `` inside my transcript fixture?'] != [the prompt]
test_fenced_system_reminder_example_is_user_text
the reminder block was deleted from inside the fence
test_host_only_transcript_does_not_take_a_session_slot
['host-note'] != ['real-task'] (limit=1, host-only record newer)
After: 139 passed, 1 skipped, 12 subtests. Full suite: 1,809 passed, 11
skipped, 359 subtests.
---
skillopt_sleep/harvest.py | 41 ++++++++++++++--------
tests/test_sleep_engine.py | 72 +++++++++++++++++++++++++++++++-------
2 files changed, 87 insertions(+), 26 deletions(-)
diff --git a/skillopt_sleep/harvest.py b/skillopt_sleep/harvest.py
index 8ecf6e2e4..da0ac9dc4 100644
--- a/skillopt_sleep/harvest.py
+++ b/skillopt_sleep/harvest.py
@@ -143,11 +143,12 @@ def _detect_feedback(text: str) -> List[str]:
# session, so the wrapper is unwrapped instead of dropped.
_PASTED_CONTENT_RE = re.compile(r"?pasted_content[^>]*>", re.IGNORECASE)
-# Host-injected status notes and environment blocks. A message that is only
-# a reminder is host noise, and a reminder prefixed to a real request must
-# not ride into the mined intent as text the user never wrote.
+# Host-injected status notes. A message that is only a reminder is host noise,
+# and a reminder *prefixed* to a real request must not ride into the mined
+# intent as text the user never wrote. The match is anchored by the caller:
+# only the block that leads a message is removed.
_SYSTEM_REMINDER_RE = re.compile(
- r".*?\s*", re.IGNORECASE | re.DOTALL
+ r"]*>.*?", re.IGNORECASE | re.DOTALL
)
# Tags the host injects as a whole user message: expanded slash-command
@@ -170,14 +171,25 @@ def _detect_feedback(text: str) -> List[str]:
def _strip_host_wrappers(text: str) -> str:
- """Unwrap pasted user content and drop host-injected blocks.
-
- Only the two wrappers above are touched: a message keeps its text when it
- is wrapped in a tag the host does not own.
+ """Unwrap pasted user content and drop the host's own leading notes.
+
+ ```` tags only ever wrap text the user typed, so both the
+ opening and the closing tag are removed wherever they appear. A
+ ```` block is removed only while it *leads* the message,
+ which is how the host prepends it. The same text inside a prompt the user
+ wrote — an inline-code sample, a fenced XML example — is their content and
+ survives, so the harvested request stays what they typed.
"""
if "<" not in text:
return text
- return _PASTED_CONTENT_RE.sub("", _SYSTEM_REMINDER_RE.sub("", text))
+ out = _PASTED_CONTENT_RE.sub("", text)
+ while True:
+ stripped = out.lstrip()
+ match = _SYSTEM_REMINDER_RE.match(stripped)
+ if match is None:
+ break
+ out = stripped[match.end():]
+ return out
def _is_meta_prompt(text: str) -> bool:
@@ -267,11 +279,12 @@ def _is_headless_replay(digest: "SessionDigest") -> bool:
if digest.n_user_turns > 1:
return False
if digest.n_user_turns == 0:
- # Every user turn was filtered as host noise or engine input. That is
- # not a replay signal by itself: a session whose user turns were all
- # host-wrapped still holds real assistant work (#318), so only a
- # transcript with no work at all counts as engine-generated.
- return digest.n_assistant_turns == 0
+ # Every user turn was filtered as host noise or engine input, so there
+ # is no user intent to mine. Such a record must not be admitted: it
+ # would take a slot under --max-sessions and yield no task, displacing
+ # a real session (review of #318). A pasted brief is not this case —
+ # it now survives the filter as a user turn.
+ return True
prompt = digest.user_prompts[0] if digest.user_prompts else ""
for marker in _REPLAY_PROMPT_MARKERS:
if marker in prompt:
diff --git a/tests/test_sleep_engine.py b/tests/test_sleep_engine.py
index 91662ba52..71476365b 100644
--- a/tests/test_sleep_engine.py
+++ b/tests/test_sleep_engine.py
@@ -191,18 +191,66 @@ def test_system_reminder_prefix_does_not_ride_into_the_intent(self):
self.assertEqual(task.intent, request)
self.assertNotIn("Status note", task.intent)
- def test_session_without_user_turns_is_a_replay_only_when_it_has_no_work(self):
- from skillopt_sleep.harvest import _is_headless_replay
-
- # issue #318: all user turns filtered out is not a replay signal on
- # its own — real assistant work means the session is a real one.
- worked = SessionDigest(session_id="s", project="/repo/example",
- assistant_finals=["Done."],
- n_user_turns=0, n_assistant_turns=2)
- empty = SessionDigest(session_id="s", project="/repo/example")
-
- self.assertFalse(_is_headless_replay(worked))
- self.assertTrue(_is_headless_replay(empty))
+ def test_host_only_transcript_does_not_take_a_session_slot(self):
+ from skillopt_sleep.harvest import harvest
+
+ # Review of #318: a record with no user intent must not be admitted
+ # before the cap. It mines nothing, so admitting it turns a night into
+ # zero tasks by displacing a real session with a newer host-only one.
+ with tempfile.TemporaryDirectory() as tmp:
+ project_dir = os.path.join(tmp, "projects", "-repo-example")
+ os.makedirs(project_dir)
+ host_only = os.path.join(project_dir, "host-note.jsonl")
+ self._write_jsonl(host_only, [
+ {"type": "user", "isMeta": True,
+ "timestamp": "2026-10-15T09:00:00Z",
+ "cwd": "/repo/example", "sessionId": "host-note",
+ "message": {"role": "user",
+ "content": "ok"}},
+ {"type": "assistant", "timestamp": "2026-10-15T09:00:30Z",
+ "cwd": "/repo/example", "sessionId": "host-note",
+ "message": {"role": "assistant", "content": "Noted."}},
+ ])
+ real = os.path.join(project_dir, "real-task.jsonl")
+ self._write_claude_session(real, [
+ ("user", "Fix the failing date parser test in module B."),
+ ("assistant", "Fixed."),
+ ])
+ os.utime(host_only, (2_000_000, 2_000_000)) # newer
+ os.utime(real, (1_000_000, 1_000_000))
+
+ digests = harvest(os.path.join(tmp, "projects"), scope="all", limit=1)
+
+ self.assertEqual([d.session_id for d in digests], ["real-task"])
+
+ def test_inline_code_system_reminder_example_is_user_text(self):
+ # Review of #318: the user quoting a reminder inside inline code was
+ # being deleted, which changed both the request and the mined intent.
+ request = ("How should I escape `note` "
+ "inside my transcript fixture?")
+ with tempfile.TemporaryDirectory() as tmp:
+ path = os.path.join(tmp, "inline-example.jsonl")
+ self._write_claude_session(path, [
+ ("user", request),
+ ("assistant", "Use html.escape()."),
+ ])
+ digest = digest_transcript(path)
+
+ self.assertEqual(digest.user_prompts, [request])
+
+ def test_fenced_system_reminder_example_is_user_text(self):
+ request = ("This is what our host writes before a real request:\n"
+ "```xml\n\ncontext\n\n```\n"
+ "Please make the parser strip it.")
+ with tempfile.TemporaryDirectory() as tmp:
+ path = os.path.join(tmp, "fenced-example.jsonl")
+ self._write_claude_session(path, [
+ ("user", request),
+ ("assistant", "Will do."),
+ ])
+ digest = digest_transcript(path)
+
+ self.assertEqual(digest.user_prompts, [request])
def _claude_session_with_injected_messages(self, tmp, follow_up):
"""A Claude Code session as written to disk when a skill is loaded.