feat(tasks): the hand-off — SessionEnd releases a session's claims; the practice is written down (milestone 381 step 4)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 59s
CI & Build / Python tests (push) Successful in 1m40s
CI & Build / Build & push image (push) Successful in 24s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 59s
CI & Build / Python tests (push) Successful in 1m40s
CI & Build / Build & push image (push) Successful in 24s
Release is the mechanical half: scribe_session_end.sh sends the ending
session's id to /api/plugin/release-session, which releases the claims it
held. A tidy-up, not the guarantee (no SessionEnd on a crash; the lease
covers that), and skipped on /clear so SessionStart(clear) can still
hand the claimed work back.
Saying what happened is the half only the model can do. It is stated as a
practice where it is read: the using-scribe skill owns it ("Hand off before
this session's context stops existing", pinned in test_guidance_ownership),
the static context points at it for the wrap-up moment, and add_task_log's
docstring says a log claims the task. _INSTRUCTIONS is untouched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "scribe",
|
||||
"description": "Scribe for Claude Code: connects the scribe MCP server, adds the hooks that deliver live project state and relevant records at the right moment, ships the shared client-neutral Scribe skills (using-scribe, writing-plans, reporting-back, systematic-debugging, verification, brainstorming, reusing-code, shape-accounting), and syncs your saved Scribe Processes as skills (/scribe:sync).",
|
||||
"version": "2026.09.24.1041",
|
||||
"version": "2026.09.24.1042",
|
||||
"author": {
|
||||
"name": "Bryan Van Deusen"
|
||||
},
|
||||
|
||||
@@ -101,6 +101,16 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionEnd": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/scribe_session_end.sh\""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
# Scribe — release this session's task claims as it ends (milestone 381 step 4).
|
||||
#
|
||||
# A claim records a session's attention, and a session that ends has none left
|
||||
# to give. This is the mechanical half of the hand-off: it needs nothing from
|
||||
# the model, only the session id the harness reports. The other half — writing
|
||||
# down where the work stands — only the model can do, and it is stated as a
|
||||
# practice in the skill and the static context, not here.
|
||||
#
|
||||
# A TIDY-UP, NOT THE GUARANTEE. SessionEnd does not fire on a crash, a killed
|
||||
# terminal or a dropped connection, and those are exactly the cases the claim
|
||||
# was designed around. The lease is what makes a dead session's claim read as
|
||||
# dead; this only keeps the ordinary exit from leaving a claim to run out.
|
||||
#
|
||||
# NOT ON /clear. A clear ends one conversation and starts the next in the same
|
||||
# terminal, and SessionStart(source=clear) pushes back the work this session
|
||||
# had claimed. Releasing here would leave that push with nothing to say. The
|
||||
# next write moves the claim wherever the work actually continues.
|
||||
#
|
||||
# EXIT 0 AND SILENT, ALWAYS. Nobody reads a SessionEnd hook's output, and the
|
||||
# session is ending whether this succeeds or not.
|
||||
set -uo pipefail
|
||||
|
||||
# shellcheck source=plugin/hooks/scribe_defs.sh
|
||||
. "$(dirname "${BASH_SOURCE[0]}")/scribe_defs.sh"
|
||||
|
||||
command -v curl >/dev/null 2>&1 || exit 0
|
||||
scribe_config || exit 0
|
||||
|
||||
event=$(cat 2>/dev/null || true)
|
||||
[ -n "$event" ] || exit 0
|
||||
|
||||
event_flat=$(printf '%s' "$event" | scribe_json_flat)
|
||||
reason=$(scribe_json_pick "$event_flat" '.reason')
|
||||
[ "$reason" = "clear" ] && exit 0
|
||||
|
||||
session_id=$(scribe_json_pick "$event_flat" '.session_id')
|
||||
[ -n "$session_id" ] || exit 0
|
||||
|
||||
sid_enc=$(printf '%s' "$session_id" | scribe_urlenc) || exit 0
|
||||
curl -fsS --max-time 4 \
|
||||
-H "Authorization: Bearer ${token}" \
|
||||
"${url%/}/api/plugin/release-session?session_id=${sid_enc}" \
|
||||
>/dev/null 2>&1 || true
|
||||
exit 0
|
||||
@@ -23,6 +23,9 @@ What only Claude Code needs said:
|
||||
long session, log it to Scribe, then tell the operator it's a good moment to
|
||||
`/compact` and name what you logged. You can't run it yourself; suggest it at
|
||||
seams, not every turn.
|
||||
- **When the operator wraps up, hand off first.** Log where each task you
|
||||
worked stands before the session ends; the plugin releases your claims at
|
||||
session end, but only you can say what happened (using-scribe, "Hand off").
|
||||
- **Stored Processes arrive as skills** (`scribe-proc-*`), refreshed at session
|
||||
start. After a Process is added or edited, `/scribe:sync` makes it available
|
||||
straight away.
|
||||
|
||||
@@ -175,6 +175,16 @@ Two constraints on *how* that's achieved:
|
||||
**complete** a task and when you **hit or discover a problem**, so a change
|
||||
of direction is on the record and not only the successes.
|
||||
|
||||
**Hand off before this session's context stops existing.** A compaction, a
|
||||
`/clear`, the operator wrapping up for the day — each is the last moment the
|
||||
reasoning behind the work lives anywhere but here. Log on the task you were
|
||||
holding where it stands, what you tried and ruled out, and the next move:
|
||||
write down what the next session needs, because it arrives with Scribe's
|
||||
record and nothing else. Moving a task to `in_progress` or logging on it also
|
||||
claims it for this session — that claim is what hands the work back to you
|
||||
after a compaction, and it ends on its own when you stop, so there is nothing
|
||||
to release by hand.
|
||||
|
||||
6. **Fixes are issues, not work-logs.** When you fix a problem — even one solved
|
||||
in passing — record it as its own issue (`create_task(kind="issue")`) with
|
||||
symptom → root cause → fix, optionally linked to the task it arose from
|
||||
|
||||
Reference in New Issue
Block a user