feat(tasks): a task can say a session is working it — the claim (milestone 381 step 2)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / integration (push) Successful in 53s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / Python tests (push) Failing after 1m18s
CI & Build / Build & push image (push) Skipped

status is durable and nothing clears it, so in_progress cannot also mean
"someone is on this now". The claim is that second meaning, stored as who
and when so it dies on read rather than needing to be cleared.

- notes.claimed_by / claimed_at / claim_touched_at / claim_session (0110).
- The server stamps it on the write that is the work: reaching in_progress
  (update or create, via apply_status_transition) and a work log on an open
  task. done/cancelled/todo release it. Live while touched within
  CLAIM_LEASE (2h); dead on read past it, with no sweep.
- The plugin's PostToolUse hook on update_task/add_task_log binds the
  harness's session_id (GET /api/plugin/claim-session). It binds only to a
  live claim the caller holds; it cannot create one.
- to_dict carries `claim`. Plugin version minted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 19:10:06 -04:00
co-authored by Claude Opus 5.5
parent b8f543f45a
commit b06b3a1d8a
12 changed files with 473 additions and 6 deletions
+30
View File
@@ -15,6 +15,7 @@ from scribe.config import Config
from scribe.services import plugin_context as plugin_ctx_svc
from scribe.services import repo_bindings as repo_bindings_svc
from scribe.services import report_check as report_check_svc
from scribe.services import task_claims as task_claims_svc
from scribe.services.settings import get_admin_setting, set_setting
plugin_bp = Blueprint("plugin", __name__, url_prefix="/api/plugin")
@@ -352,6 +353,35 @@ async def report_check():
return jsonify(body)
@plugin_bp.get("/claim-session")
@login_required
async def claim_session():
"""Bind the harness's session id to the caller's live claim on a task (milestone 381).
Called by `scribe_claim_session.sh` after `update_task` / `add_task_log`.
The server has already stamped the claim on that write; this only says
WHICH session made it — an id the harness reported, not one the model
asserted. A GET for the reason every plugin endpoint is one: a read-scoped
key must be enough to run the plugin.
Query:
task_id (int) — the task the tool call named.
session_id (str) — the Claude Code session id from the hook event.
Returns the claim when one was bound, `{"claim": null}` when there was
nothing to bind to (no live claim of the caller's, or not writable).
"""
try:
task_id = int(request.args.get("task_id") or "")
except ValueError:
return jsonify({"error": "task_id must be an integer"}), 400
session_id = (request.args.get("session_id") or "").strip()
if not session_id:
return jsonify({"error": "session_id is required"}), 400
claim = await task_claims_svc.bind_session(g.user.id, task_id, session_id)
return jsonify({"claim": claim})
@plugin_bp.get("/processes")
@login_required
async def process_manifest():