Conversation
`task.checklistitem.{complete,renew,delete}` take positional
`[taskId, itemId]` but resolve the item by id ALONE — the task id on the
wire is decorative. Verified against a live portal (2026-09-03):
b24_task_checklist_item_complete { taskId: 4193, itemId: 1341 }
where item 1341 belongs to task 4191, answered
`{"completed": true, "taskId": 4193}` — and a listing of task 4191 then
showed that item ticked with `toggledBy` set. A nonexistent id is
acknowledged just as cheerfully: `{"completed": true, "itemId": 999999}`.
So an agent that mixes up two ids silently edits a different task's
checklist and is told it succeeded. That is the worst failure shape we
can ship: a write to the wrong place, reported as a write to the right
one.
All three verbs, single and batch, now read the task's checklist once per
call and refuse ids that aren't on it with the new `ITEM_NOT_ON_TASK`
code, naming the strays and pointing at `b24_task_checklist_item_list`.
The delete path already made that read for heading detection, so the
cost there is unchanged; complete / renew pay one extra round-trip, which
is the price of `completed: true` meaning what it says.
Fail-soft convention preserved: if the pre-flight returns something that
isn't a list, the check stands down and the real call's own error is the
signal. `confirmDeleteHeading: true` still waives the cascade check — it
does not waive membership.
The heading pre-flight and the membership check now share a single
`getlist`, so an unconfirmed heading delete still makes exactly one
read. Tests: cross-task refusal (single + batch), nonexistent id,
stand-down when the pre-flight shape drifts, and the existing heading
gate kept green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
task.checklistitem.{complete,renew,delete}resolve the item by id alone — thetaskIdthey take positionally is decorative. Passing task A with an item id from task B returns success and acts on task B. All three verbs now pre-flight the task's checklist and refuse ids that aren't on it (ITEM_NOT_ON_TASK).Type of change
Linked issue
Checklist
pnpm lintpassespnpm typecheckpassespnpm testpassesdocs/andskills/(adding-tools.mdgains a "membership pre-flight" note next to the delete registry — it's a pattern worth copying wherever a REST method takes a parent id it doesn't enforce)Screenshots / logs
Live portal, before. Item 1341 belongs to task 4191; the call names task 4193:
A nonexistent id behaves the same way:
complete { taskId: 4193, itemId: 999999 }→{"completed": true}.After:
The normal path is unchanged — completing, renewing and deleting real items on their own task all still work, verified on the same portal.
Notes for reviewers
The trade-off is one extra
getlistper complete / renew call. I think it's clearly worth it — the alternative is a tool that writes to the wrong task and reports success — but it is a real cost and you may want it configurable, so here's the reasoning to push back on:assertNotHeading.confirmDeleteHeading: truewaives the cascade check only. I deliberately did not let it waive membership: "I agree to wipe this checklist" is not "I agree to wipe a checklist on some other task".If you'd rather keep complete / renew at zero extra round-trips, the alternative I considered was validating only when the id looks suspicious — but there is no such signal in the input, so it would amount to not validating.
New error code
ITEM_NOT_ON_TASKis registered inBitrix24ErrorCodewith the registry-completeness test updated, per the code-registry procedure in the skill.