From c40e9f01e122f94a46d0164b6e89a9c9a266df84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:52:19 +0000 Subject: [PATCH 1/2] Initial plan From e98a723a6857f097e76834a6fb44946a720c31c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:44:37 +0000 Subject: [PATCH 2/2] fix: auto-close linear linkback issue shells Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com> --- .github/workflows/issue-triage.yml | 7 +- tests/unit/test_issue_triage_workflow.py | 88 ++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_issue_triage_workflow.py diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index 76250dd72..2bb322c2f 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -28,12 +28,17 @@ jobs: script: | const issue = context.payload.issue; const body = (issue.body || '').trim(); + const bodyWithoutLinearLinkback = body + .replace(//gi, '') + .replace(/

\s*[^<]*<\/a>\s*<\/p>/gi, '') + .trim(); + const linearLinkbackOnly = body !== '' && bodyWithoutLinearLinkback === ''; // Bot-opened issues with no body are agent-session artifacts (e.g. the // recurring empty "Addressing Issues" shells from Jules/Linear sessions, // #1548/#1553/#1568). Labeling them for triage just manufactures noise // that syncs back into Linear, so close them instead. - if (issue.user && issue.user.type === 'Bot' && body === '') { + if (issue.user && issue.user.type === 'Bot' && (body === '' || linearLinkbackOnly)) { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, diff --git a/tests/unit/test_issue_triage_workflow.py b/tests/unit/test_issue_triage_workflow.py new file mode 100644 index 000000000..a9951d9be --- /dev/null +++ b/tests/unit/test_issue_triage_workflow.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +import json +import shutil +import subprocess +import textwrap +from pathlib import Path + +import pytest +import yaml + +WORKFLOW_PATH = Path(__file__).resolve().parents[2] / ".github/workflows/issue-triage.yml" + + +def _load_workflow() -> dict: + assert WORKFLOW_PATH.exists(), "Issue triage workflow should exist" + return yaml.safe_load(WORKFLOW_PATH.read_text()) + + +def _get_script(workflow: dict) -> str: + return workflow["jobs"]["triage"]["steps"][0]["with"]["script"] + + +def test_issue_triage_workflow_file_is_valid_yaml() -> None: + workflow = _load_workflow() + assert workflow["name"] == "Issue Triage" + + +@pytest.mark.skipif(shutil.which("node") is None, reason="node is required") +def test_issue_triage_closes_linear_linkback_only_bot_shells(tmp_path: Path) -> None: + script = _get_script(_load_workflow()) + script_path = tmp_path / "issue_triage.js" + script_path.write_text(script) + + harness = tmp_path / "harness.mjs" + harness.write_text( + textwrap.dedent( + """ + import fs from 'fs'; + const script = fs.readFileSync(process.argv[2], 'utf8'); + + const calls = { addLabels: 0, update: 0, comments: [] }; + const github = { + rest: { + issues: { + addLabels: async () => { calls.addLabels += 1; }, + update: async () => { calls.update += 1; }, + createComment: async (payload) => { calls.comments.push(payload.body); }, + }, + }, + }; + + const context = { + repo: { owner: 'o', repo: 'r' }, + payload: { + issue: { + number: 1553, + title: 'Addressing Issues', + body: '\\n

GRV-423

', + user: { type: 'Bot' }, + }, + }, + }; + + await new Function( + 'github', 'context', + `return (async () => { ${script} })()` + )(github, context); + + process.stdout.write(JSON.stringify(calls)); + """ + ).strip() + ) + + result = subprocess.run( + ["node", str(harness), str(script_path)], + capture_output=True, + text=True, + timeout=60, + ) + assert result.returncode == 0, f"harness failed: {result.stderr}" + + calls = json.loads(result.stdout) + assert calls["update"] == 1, "linkback-only bot shells should be auto-closed" + assert calls["addLabels"] == 0, "auto-closed bot shells must not be triage-labeled" + assert any( + "Closing automatically" in comment for comment in calls["comments"] + ), "auto-close comment should be posted"