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: '\\nGRV-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"