skein_update
Patch an existing issue. Supports field updates (title / description / type / priority / assignee / due dates / design / acceptance / notes / external ref / source context / estimated minutes), status transitions, component-set replace, and label add/remove — all in one tool call. Required: id (full hierarchical) and expectedVersion for optimistic concurrency. On version mismatch returns {error:"version_conflict", currentVersion:<int>}. Locks are honoured: caller must hold the lock or pass force=true (force_unlock: project owner, space owner, instance admin). This tool does NOT touch the lock — to release the lock use skein_unclaim(id, nextStatus, reason) (atomic over lock+status) or skein_close (terminal close). Multi-op note: when the call sets a mix of fields, status, components, and labels, each sub-operation is committed independently — a failure mid-way leaves a partial state and you should re-fetch with skein_show before retrying. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.
A transaction abort with an unchanged declared version returns serialization_retry. Earlier sub-operations may have committed: re-read and resend only unapplied changes.
closeOutcome is required when transitioning to closed; repeating a close without it preserves the previous outcome.
Text patches (description, design, acceptanceCriteria, notes, externalRef, sourceContext): omitted leaves unchanged; null or an empty string clears. Other non-string values are refused before any write with validation_error, code text_field_not_string and field naming the argument.
⚠ Field bleed: tool-call wrapper literals (<invoke, <parameter name=, </parameter>) in the prose fields (description / acceptanceCriteria / notes / design) are rejected with validation_error (code: tool_call_syntax_leaked) — a hit means you closed a parameter block with the wrong tag. Closed set + the look-alikes that are NOT rejected: skein_get_instructions ("Text fields — forbidden literals").
Tool call template
Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.
{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"arguments": {
"id": "<id>",
"expectedVersion": "<expectedVersion>"
},
"name": "skein_update"
}
}Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Full hierarchical issue id (e.g. "demo-42"). | |
| type | No | Change issue type. | |
| dueAt | No | RFC3339 timestamp; replaces due date. | |
| force | No | Bypass lock + version + pinned predicates. Needs force_unlock on the issue's project (project owner, space owner, instance admin); others get `force_unlock_forbidden`. Default: false. | |
| notes | No | Replace notes. | |
| title | No | New title (1..1000 chars). | |
| design | No | Replace design notes. | |
| reason | No | Free-form reason annotation; only meaningful when `status` is set (lands in events.reason and, for closed, issues.closed_reason). | |
| status | No | Status transition. Routed through UpdateStatus. `standing` promotes the issue: STANDING task = perpetual obligation: acted on via spawned children (skein_create parentId=<this>), never claimed or worked in place, excluded from open-work counts / skein_ready / inbox. The sole demote leg is back to 'open'. Rejected standing legs — `cannot_promote_locked_to_standing`, `cannot_work_standing_directly`, `cannot_defer_standing`, `nested_standing_forbidden` — are structural invariants that force does NOT bypass. See skein_get_instructions ("Standing tasks"). `blocked` is no longer accepted — blocked-ness is derived from the dep graph (use skein_dep_add with type='blocks' or 'waits-for' on the issue you want labelled blocked). | |
| verbose | No | Return the full IssueDetail, prose included. Default false = acknowledgement: `id`, `ok`, `version` plus the post-state stamps that apply (`status`, `assignee`, lock / claim / close stamps) — no issue card and no free text. The version is always there, so the expectedVersion cycle needs no extra read. Details: skein_get_instructions. If the write applied but its card could not be read back, the answer is the acknowledgement plus `cardUnavailable: true` — read the card with skein_show; do not repeat the write. | |
| assignee | No | Replace assignee handle. Pass an empty string (or null) to clear. A user UUID that does not name a person who may work in the project is refused with assignee_not_allowed and nothing is written; a username or email is stored as text. A non-string value (number, array, object, boolean) is refused with validation_error, code assignee_not_string, and nothing is written. | |
| priority | No | Priority 0..4. | |
| labelsAdd | No | Labels to add (idempotent — already-present labels are no-ops). | |
| deferUntil | No | RFC3339 timestamp; replaces defer-until date. | |
| closeTarget | No | The issue this one duplicates (duplicate) or that replaced it (superseded); creates that edge in the same transaction. | |
| description | No | Replace description body. | |
| externalRef | No | Replace external-system reference. | |
| closeOutcome | No | How the issue ENDED. Only done counts toward progress; the other three are excluded from scope. | |
| componentIds | No | Replace the issue's component set. UUID strings; must belong to the issue's project. | |
| labelsRemove | No | Labels to remove (must currently be set on the issue). | |
| sourceContext | No | Replace source-context annotation. | |
| reviewRequired | No | Whether this issue needs the AUTHOR's review once it is closed. Only the issue's author may set it — anyone else, admins included, gets `review_forbidden`. It does NOT travel the ordinary update path: send it ALONE. Combined with any other changing argument (a field, `status`, `reason`, `componentIds`, `labelsAdd` / `labelsRemove`) the whole call is refused with `validation_error` and nothing is written — the flag is never applied half-way. Flipping it moves neither `version` nor the issue's updated-at stamp, so the acknowledgement's version is the one you already held; `expectedVersion` is still required and still checked, as the assertion that you are flipping the state you read. Clearing the flag is NOT a review mark — it leaves any existing mark untouched. There is deliberately no agent tool for the mark itself. The acknowledgement of this branch is always the lean one — `verbose=true` does NOT widen it to the card, because the card would carry the mechanism's own keys. Read them with `skein_show`. | |
| expectedVersion | Yes | The version the agent expects to see on the issue. The update only proceeds when the live version matches; otherwise returns version_conflict with the live version. | |
| estimatedMinutes | No | Replace estimated-minutes (>=0). | |
| acceptanceCriteria | No | Replace acceptance criteria. |