Skip to content

Prevent issue-intent labels from removing existing labels - #53672

Merged
pelikhan merged 6 commits into
mainfrom
copilot/fix-graph-ql-intent-path-label-removal
Aug 18, 2026
Merged

Prevent issue-intent labels from removing existing labels#53672
pelikhan merged 6 commits into
mainfrom
copilot/fix-graph-ql-intent-path-label-removal

Conversation

Copilot AI commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

The add_labels GraphQL intent path could remove an existing label when its requested confidence fell below the repository automation threshold. It also reported requested labels as added even when GitHub retained them only as suggestions.

  • Prevent destructive no-ops

    • Exclude already-applied labels before invoking the intent mutation.
    • Skip the mutation when no new labels remain.
  • Defend existing state

    • Compare the mutation result with the pre-existing label set.
    • Restore any missing existing labels through the add-only REST endpoint and emit a warning.
  • Report observed outcomes

    • Derive labelsAdded from the returned label state.
    • Record gated labels separately as labelsSuggested.
    • Persist both fields in the safe-output manifest.
  • Regression coverage

    • Cover already-applied labels, confidence-gated suggestions, and restoration after partial replacement.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix GraphQL intent path removing applied labels Prevent issue-intent labels from removing existing labels Aug 18, 2026
Copilot AI requested a review from pelikhan August 18, 2026 12:23
@pelikhan
pelikhan marked this pull request as ready for review August 18, 2026 12:24
Copilot AI balanced review requested due to automatic review settings August 18, 2026 12:24
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Ponytail Reviewer completed successfully!

Generated by Ponytail Reviewer for #53672

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #53672 does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold is 100).

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

Copy link
Copy Markdown
Contributor

PR Triage

Category: chore (WIP) | Risk: low | Priority: low (score 20)

  • Draft, opened minutes ago with 0 diff — agent still generating changes (Fix GraphQL intent path removing applied labels)
  • Batch: pr-batch:wip-drafts (6 similar just-opened WIP PRs)

Recommended action: defer — recheck once agent finishes and diff is populated

Generated by 🔧 PR Triage Agent · auto · 53.1 AIC · ⌖ 2.52 AIC · ⊞ 8.3K ·

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

reviewed_at: 2026-08-18T12:27:00Z
review_event: COMMENT
top_themes:
  - no actionable issues found in changed lines
files_reviewed:
  - actions/setup/js/add_labels.cjs
  - actions/setup/js/add_labels.test.cjs
  - actions/setup/js/safe_output_manifest.cjs
  - actions/setup/js/safe_output_manifest.test.cjs
comment_count: 0

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · gpt54 · 4.9 AIC · ⌖ 8.68 AIC · ⊞ 7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: non-blocking

I don't see a changed-line correctness or maintainability issue here severe enough to block merge.

Reviewed themes
  • The GraphQL intent path now avoids re-proposing labels that are already present.
  • The fallback restoration via REST covers the destructive replacement behavior this PR is fixing.
  • The new labelsSuggested plumbing is propagated through manifest logging and covered by tests.
  • Regression coverage hits the important cases in the diff: already-applied labels, confidence-gated suggestions, and restoration after partial replacement.

🔎 Code quality review by PR Code Quality Reviewer · gpt54 · 4.9 AIC · ⌖ 8.68 AIC · ⊞ 7K
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation is correct and well-covered by new tests. The mergedSpecs dedup removal is safe because the caller pre-filters newLabelSpecs to exclude already-existing labels before calling applyIssueIntentLabels. The REST recovery fallback and labelsSuggested propagation through the manifest are complete and properly tested.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 28.2 AIC · ⌖ 8.76 AIC · ⊞ 5.7K

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ponytail review (over-engineering only): one minor nit found.

net: -2 lines possible.

Generated by ✂️ Ponytail Reviewer for #53672 · auto · 21.2 AIC · ⌖ 4.29 AIC · ⊞ 7.3K
Comment /ponytail to run again

const labelsAdded = newLabelSpecs.filter(spec => afterNamesLower.has(spec.name.toLowerCase())).map(spec => spec.name);
const labelsSuggested = newLabelSpecs.filter(spec => hasLabelIntentMetadata(spec) && !afterNamesLower.has(spec.name.toLowerCase())).map(spec => spec.name);

if (newLabelSpecs.length === 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L412-413: delete: redundant "No new labels to add" info log. Line 415's "Successfully added 0 labels" already conveys this.

@github-actions github-actions Bot mentioned this pull request Aug 18, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents issue-intent label operations from deleting existing labels and improves outcome reporting.

Changes:

  • Skips already-applied labels and restores unexpectedly removed labels.
  • Separates applied labels from confidence-gated suggestions.
  • Adds regression and manifest persistence coverage.
Show a summary per file
File Description
actions/setup/js/add_labels.cjs Adds filtering, recovery, and observed outcome reporting.
actions/setup/js/add_labels.test.cjs Covers no-ops, suggestions, and restoration.
actions/setup/js/safe_output_manifest.cjs Persists suggested labels.
actions/setup/js/safe_output_manifest.test.cjs Tests label outcome persistence.

Review details

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread actions/setup/js/add_labels.cjs Outdated
Comment on lines +81 to +82
const existingLabelNames = normalizeLabelNames(issueData.labels || []);
const mergedSpecs = [...labelSpecs, ...existingLabelNames.filter(name => !requestedNamesLower.has(name.toLowerCase())).map(name => ({ name }))];
const mergedSpecs = [...labelSpecs, ...existingLabelNames.map(name => ({ name }))];

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — requesting changes on two correctness issues and one test-fidelity concern.

📋 Key Themes & Highlights

Issues Found

  1. Silent label loss (add_labels.cjs:410) — Plain-name specs that the mutation drops are tracked in neither labelsAdded nor labelsSuggested. The labelsSuggested bucket only captures specs with intent metadata, so a { name: "foo" } spec that fails to apply disappears silently.

  2. Duplicate entries in mergedSpecs (add_labels.cjs:82) — The old de-duplication guard was removed. If any caller-supplied labelSpec overlaps with existingLabelNames, the mutation receives the same label twice.

  3. Test assertion may be testing the wrong snapshot (add_labels.test.cjs:350) — The "restore pre-existing labels" test checks result.after_state.labels, but after_state is populated by a post-mutation fetchIssueState call that uses the default mock. The test should re-mock the GET to return the fully-restored set, or assert labelsAdded / afterLabels directly.

Positive Highlights

  • ✅ Excellent defensive recovery — detect missing existing labels post-mutation and restore them via REST before returning.
  • ✅ Clean separation of labelsAdded vs labelsSuggested in both the result and the manifest.
  • ✅ Three targeted regression tests for the three main failure modes — good test-first thinking.
  • ✅ Mutation is skipped entirely when all requested labels are already applied — avoids unnecessary round-trips.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 54.3 AIC · ⌖ 10.3 AIC · ⊞ 7.8K
Comment /matt to run again

: existingLabels;
const afterNamesLower = new Set(afterLabels.map(name => name.toLowerCase()));
const labelsAdded = newLabelSpecs.filter(spec => afterNamesLower.has(spec.name.toLowerCase())).map(spec => spec.name);
const labelsSuggested = newLabelSpecs.filter(spec => hasLabelIntentMetadata(spec) && !afterNamesLower.has(spec.name.toLowerCase())).map(spec => spec.name);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] Silent loss: plain-name newLabelSpecs entries dropped by the mutation land in neither labelsAdded nor labelsSuggested.

labelsSuggested is gated on hasLabelIntentMetadata(spec), so a { name: "foo" } spec that the mutation silently drops vanishes from the result with no trace — callers can't distinguish "applied", "suggested", and "silently dropped".

💡 Suggested fix

Capture all unapplied new labels and split them afterwards:

const labelsAdded      = newLabelSpecs.filter(spec =>  afterNamesLower.has(spec.name.toLowerCase())).map(s => s.name);
const labelsNotApplied = newLabelSpecs.filter(spec => !afterNamesLower.has(spec.name.toLowerCase()));
const labelsSuggested  = labelsNotApplied.filter(spec => hasLabelIntentMetadata(spec)).map(s => s.name);
// optionally warn on labelsNotApplied that are not suggested

A regression test with a metadata-free spec that the mutation silently drops would lock this in.

@copilot please address this.

Comment thread actions/setup/js/add_labels.cjs Outdated
const requestedNamesLower = new Set(labelSpecs.map(spec => spec.name.toLowerCase()));
const existingLabelNames = normalizeLabelNames(issueData.labels || []);
const mergedSpecs = [...labelSpecs, ...existingLabelNames.filter(name => !requestedNamesLower.has(name.toLowerCase())).map(name => ({ name }))];
const mergedSpecs = [...labelSpecs, ...existingLabelNames.map(name => ({ name }))];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] mergedSpecs still duplicates existing labels when the caller passes specs that overlap with existingLabelNames.

After the change, mergedSpecs = [...labelSpecs, ...existingLabelNames.map(name => ({ name }))] no longer de-duplicates. If any labelSpec name matches an existing label, the mutation receives that label twice. Downstream buildIssueIntentLabelUpdates may or may not handle duplicates; if it doesn't, the mutation payload could be malformed or trigger unexpected behavior.

💡 Suggested fix

Keep the original de-duplication but move it to operate only on the labels passed to the mutation, not on newLabelSpecs (the caller already filters out already-applied labels):

const existingNamesLower = new Set(labelSpecs.map(s => s.name.toLowerCase()));
const mergedSpecs = [
  ...labelSpecs,
  ...existingLabelNames
    .filter(name => !existingNamesLower.has(name.toLowerCase()))
    .map(name => ({ name })),
];

This mirrors the original dedup logic and prevents double-entries in the mutation payload.

@copilot please address this.

nodes: [
{ id: "LABEL_feature-openapi", name: "feature-openapi" },
{ id: "LABEL_area-minimal", name: "area-minimal" },
{ id: "LABEL_bug", name: "bug" },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The "restore pre-existing labels" test asserts result.after_state.labels but the mock addLabels REST call returns { data: [...] } — the actual after_state is captured by attachExecutionState from a separate fetchIssueState call that is NOT re-mocked in this test.

This means result.after_state.labels likely reflects the pre-mutation state fetched from the default mock (only feature-openapi), not the restored set. If so, the assertion expect(result.after_state.labels).toEqual(["feature-openapi", "area-minimal", "bug"]) is either coincidentally passing or testing the wrong thing.

💡 Suggested fix

Mock the afterState fetch (mockGithub.rest.issues.get returning the post-restore label set) or explicitly verify result.afterLabels (the value returned from applyIssueIntentLabels) rather than relying on the after_state snapshot populated by attachExecutionState.

@copilot please address this.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Please address the remaining blocking review feedback on this PR, then run the pr-finisher skill and report back with validation results and any remaining blockers.

  • Blocking feedback currently calls out silent loss of plain-name label specs, duplicate mergedSpecs entries, and a test that may be asserting the wrong post-restore snapshot.
  • No failed checks were listed in the compact queue for this run.
  • Branch refresh was not requested because local make fmt could not run in this sandbox (go.mod requires Go >= 1.26.5; installed Go is 1.24.13).
  • After fixes, please identify any addressed review threads or stale blocking reviews that can be resolved or dismissed.

Run: https://github.com/github/gh-aw/actions/runs/32137770714

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 20 AIC · ⌖ 7.86 AIC · ⊞ 6.4K ·
Comment /souschef to run again

Copilot AI and others added 4 commits August 18, 2026 13:07
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Please address the remaining blocking review feedback on this PR, then run the pr-finisher skill and report back with validation results and any remaining blockers.

  • Blocking feedback cur...

Addressed in 6a761e6982.

Validation:

  • npm run typecheck
  • npm run test:js -- add_labels.test.cjs --no-file-parallelism ✅ (67 tests)
  • make agent-report-progress ✅ (build, JS lint, schema freshness, impacted setup JS tests: 107 tests)
  • Secret scan ✅
  • Code review ✅ no actionable findings remaining
  • CodeQL: no analyzable CodeQL language changes detected

The duplicate mergedSpecs, silent metadata-free label loss, and restore after-state coverage feedback are addressed. CI checks are stale/empty after the agent push and need a human re-trigger before merge.

Copilot AI requested a review from gh-aw-bot August 18, 2026 13:13
@pelikhan
pelikhan merged commit d1cc5b8 into main Aug 18, 2026
9 checks passed
@pelikhan
pelikhan deleted the copilot/fix-graph-ql-intent-path-label-removal branch August 18, 2026 14:44
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.87.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add_labels: GraphQL intent path silently removes an already-applied label when confidence is below the repo automation threshold

4 participants