Summary
Two safe-output handlers send issue-intent metadata (rationale, confidence, suggest) with the wrong payload shape, so the metadata is silently dropped by the GitHub REST API (the mutation succeeds with HTTP 200, no error, the error-only fallback never fires, and no intent is persisted):
close_issue — spreads metadata as top-level siblings of a scalar state: "closed"; the API only reads close rationale when nested inside a state object.
assign_to_agent / assign_agent_helpers — sends assignees as an array of login strings and spreads metadata at the top level; the assignees endpoint only reads rationale from per-assignee objects.
The GraphQL handlers (set_issue_type, set_issue_field) and add_labels nest metadata correctly and are unaffected.
1. close_issue.cjs
closeIssue (~L161-186):
const baseParams = {
owner, repo, issue_number: issueNumber,
state: "closed", // scalar
state_reason: (stateReason || "COMPLETED").toLowerCase(),
};
if (useIssueIntent && hasIntentMetadata) {
const { data: issue } = await github.request("PATCH /repos/{owner}/{repo}/issues/{issue_number}", {
...baseParams,
...intentMetadata, // ← rationale/confidence/suggest at TOP LEVEL
});
...
}
intentMetadata ({ rationale?, confidence?, suggest? }, from normalizeIssueIntentMetadata) lands next to the scalar state, where the API ignores it. rationale is only honored when nested inside a state object.
Correct payload
Suggested fix
const { data: issue } = await github.request("PATCH /repos/{owner}/{repo}/issues/{issue_number}", {
owner, repo, issue_number: issueNumber,
state: { value: "closed", ...intentMetadata }, // nest under state
state_reason: baseParams.state_reason,
});
Keep the existing error-only fallback to github.rest.issues.update(baseParams) (scalar state) for older targets that don't accept the object form.
2. assign_agent_helpers.cjs
assignToAgent (~L399-408):
const assignParams = {
owner: targetOwner,
repo: targetRepo,
issue_number: issueNumber,
assignees: [agentLogin], // ← array of login STRINGS
};
if (useIssueIntent && intentMetadata && Object.keys(intentMetadata).length > 0) {
Object.assign(assignParams, intentMetadata); // ← rationale/etc. at TOP LEVEL
}
await githubClient.request("POST /repos/{owner}/{repo}/issues/{issue_number}/assignees", assignParams);
The POST /assignees endpoint reads intent metadata per-assignee, from an object with a login key. A plain string entry yields no metadata, and the top-level rationale is ignored.
Correct payload
Suggested fix
const assignee = useIssueIntent && intentMetadata && Object.keys(intentMetadata).length > 0
? { login: agentLogin, ...intentMetadata }
: agentLogin;
const assignParams = { owner: targetOwner, repo: targetRepo, issue_number: issueNumber, assignees: [assignee] };
await githubClient.request("POST /repos/{owner}/{repo}/issues/{issue_number}/assignees", assignParams);
Note the object form keys the assignee by login (mirroring add_labels, which keys by name).
Handlers that are correct (for reference)
set_issue_type.cjs — GraphQL updateIssue, nests under issueType: { issueTypeId, ...intentMetadata }.
set_issue_field.cjs — GraphQL setIssueFieldValue, nests metadata into each per-field issueFields[] entry.
add_labels.cjs — sends per-item { name, rationale, ... } objects.
The common contract: intent metadata must be nested inside the per-field / per-item object it annotates, never a top-level sibling.
Summary
Two safe-output handlers send issue-intent metadata (
rationale,confidence,suggest) with the wrong payload shape, so the metadata is silently dropped by the GitHub REST API (the mutation succeeds with HTTP 200, no error, the error-only fallback never fires, and no intent is persisted):close_issue— spreads metadata as top-level siblings of a scalarstate: "closed"; the API only reads closerationalewhen nested inside astateobject.assign_to_agent/assign_agent_helpers— sendsassigneesas an array of login strings and spreads metadata at the top level; the assignees endpoint only readsrationalefrom per-assignee objects.The GraphQL handlers (
set_issue_type,set_issue_field) andadd_labelsnest metadata correctly and are unaffected.1.
close_issue.cjscloseIssue(~L161-186):intentMetadata({ rationale?, confidence?, suggest? }, fromnormalizeIssueIntentMetadata) lands next to the scalarstate, where the API ignores it.rationaleis only honored when nested inside astateobject.Correct payload
{ "state": { "value": "closed", "rationale": "Short reason for closing.", "confidence": "HIGH", // optional "suggest": false // optional }, "state_reason": "not_planned" }Suggested fix
Keep the existing error-only fallback to
github.rest.issues.update(baseParams)(scalarstate) for older targets that don't accept the object form.2.
assign_agent_helpers.cjsassignToAgent(~L399-408):The
POST /assigneesendpoint reads intent metadata per-assignee, from an object with aloginkey. A plain string entry yields no metadata, and the top-levelrationaleis ignored.Correct payload
{ "assignees": [ { "login": "<agent-login>", "rationale": "Short reason.", "confidence": "HIGH", "suggest": false } ] }Suggested fix
Note the object form keys the assignee by
login(mirroringadd_labels, which keys byname).Handlers that are correct (for reference)
set_issue_type.cjs— GraphQLupdateIssue, nests underissueType: { issueTypeId, ...intentMetadata }.set_issue_field.cjs— GraphQLsetIssueFieldValue, nests metadata into each per-fieldissueFields[]entry.add_labels.cjs— sends per-item{ name, rationale, ... }objects.The common contract: intent metadata must be nested inside the per-field / per-item object it annotates, never a top-level sibling.