Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions scripts/loop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -920,12 +920,21 @@ the first `auto` run is a verification step rather than a milestone.
comment, then apply the claim label — were no-ops, and the only remaining
entry was `workflow_dispatch`, which hits the same claim check.
`humanClaimOf` (`shared/route.mjs`) now recognises exactly that event —
`issues.labeled`, the claim label, applied by an OWNER/MEMBER/COLLABORATOR,
out of `waiting-human` — and `doRun` moves the task to `ready` (timeline
`issues.labeled`, the claim label, out of `waiting-human`, applied by
anything that is not a bot — and `doRun` moves the task to `ready` (timeline
`claimed`, labels refreshed from the event) before claiming it. Narrow on
purpose: D29's repair path, `start --task` and PR `synchronize` still cannot
convert an inbox item, so the cognitive guard is unchanged and only an
explicit human act opens it.
**The first version of the predicate failed in production** on the very next
run: it gated on `event.sender.author_association`, but `author_association`
is a field of the issue, not of the sender (`simple-user` has no such field),
so the check was `undefined` and the run still logged
`skip: task #77 not claimable (status=waiting-human)`. Permissions need no
local test anyway — editing labels requires triage/write access, so the
event's mere existence is the maintainer's act; the predicate now excludes
only bots. Lesson recorded because the shape is easy to repeat: verify a
payload field against a real event payload, not against a hand-written one.

## Environment facts

Expand Down
11 changes: 9 additions & 2 deletions scripts/loop/shared/route.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,15 @@ export function humanClaimOf({ eventName, action, event = {} }, status) {
if (eventName !== 'issues' || action !== 'labeled') return null;
if (event.label?.name !== CLAIM_LABEL) return null;
if (status !== 'waiting-human') return null;
if (!OWNER_ROLES.includes(event.sender?.author_association ?? '')) return null;
return event.sender?.login ?? 'a maintainer';
// Who may apply it is settled by GitHub, not by this predicate: editing labels
// needs triage/write access, so the existence of an `issues.labeled` run is
// already a maintainer's act (a `GITHUB_TOKEN`-raised one never runs at all).
// Only bots are excluded. Do **not** test `sender.author_association`: that
// field lives on the issue, not on the sender, so the first version of this
// predicate could never be true and #77 kept skipping (D37).
const sender = event.sender ?? {};
if (sender.type === 'Bot' || String(sender.login ?? '').endsWith('[bot]')) return null;
return sender.login ?? null;
}

/**
Expand Down
Loading