From 426c726c5ae6e3ff74bf9eeab2afccaee0b6e4b2 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:51:25 +0100 Subject: [PATCH 1/4] chore(github): Add write tool for markdown report --- .github/workflows/triage-issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/triage-issue.yml b/.github/workflows/triage-issue.yml index e59a0b031e27..a65274b031a4 100644 --- a/.github/workflows/triage-issue.yml +++ b/.github/workflows/triage-issue.yml @@ -68,4 +68,4 @@ jobs: /triage-issue ${{ steps.parse-issue.outputs.issue_number }} --ci IMPORTANT: Do NOT wait for approval. claude_args: | - --max-turns 20 --allowedTools "Bash(gh api *),Bash(gh pr list *),Bash(python3 .claude/skills/triage-issue/assets/post_linear_comment.py *),Bash(rm -f /tmp/triage_report.md)" + --max-turns 20 --allowedTools "Write(//tmp/triage_report.md),Bash(gh api *),Bash(gh pr list *),Bash(python3 .claude/skills/triage-issue/assets/post_linear_comment.py *),Bash(rm -f /tmp/triage_report.md)" From 4d19b5cedd2b4f6c03305270d324c6fc59962474 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:37:20 +0100 Subject: [PATCH 2/4] chore(github): Change tool permission path --- .claude/skills/triage-issue/SKILL.md | 15 ++++++++++----- .github/workflows/triage-issue.yml | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.claude/skills/triage-issue/SKILL.md b/.claude/skills/triage-issue/SKILL.md index 763e1a6c2fbf..05b428d69fa8 100644 --- a/.claude/skills/triage-issue/SKILL.md +++ b/.claude/skills/triage-issue/SKILL.md @@ -119,24 +119,29 @@ If the issue is complex or the fix is unclear, skip this section and instead not Use the Python script at `assets/post_linear_comment.py` to handle the entire Linear API interaction. This avoids all shell escaping issues with GraphQL (`$input`, `CommentCreateInput!`) and markdown content (backticks, `$`, quotes). The script reads `LINEAR_CLIENT_ID` and `LINEAR_CLIENT_SECRET` from environment variables (set from GitHub Actions secrets), obtains an OAuth token, checks for duplicate triage comments, and posts the comment. - 1. **Write the report body to a temp file** using the Write tool (not Bash). This keeps markdown completely out of shell. + 1. **Write the report body to a file** using the Write tool (not Bash). This keeps markdown completely out of shell. - Write the triage report to `/tmp/triage_report.md`. + - **In CI:** Write to `triage_report.md` in the repository root. The CI sandbox only allows writes inside the working directory; `/tmp` and Bash output redirection are blocked. + - **Locally:** You may use `/tmp/triage_report.md` or `triage_report.md` in the repo root. 2. **Run the script:** ```bash - python3 .claude/skills/triage-issue/assets/post_linear_comment.py "JS-XXXX" "/tmp/triage_report.md" + python3 .claude/skills/triage-issue/assets/post_linear_comment.py "JS-XXXX" "triage_report.md" ``` + (Use the same path you wrote to: `triage_report.md` in CI, or `/tmp/triage_report.md` locally if you used that.) + If the script fails (non-zero exit), fall back to printing the full report to the terminal. - Clean up temp files after: + Clean up after: ```bash - rm -f /tmp/triage_report.md + rm -f triage_report.md ``` + (In CI only `triage_report.md` in the repo root is writable; use that path for write, script, and rm.) + ## Important Rules **CRITICAL — READ-ONLY POLICY:** diff --git a/.github/workflows/triage-issue.yml b/.github/workflows/triage-issue.yml index a65274b031a4..cff406aa9c59 100644 --- a/.github/workflows/triage-issue.yml +++ b/.github/workflows/triage-issue.yml @@ -68,4 +68,4 @@ jobs: /triage-issue ${{ steps.parse-issue.outputs.issue_number }} --ci IMPORTANT: Do NOT wait for approval. claude_args: | - --max-turns 20 --allowedTools "Write(//tmp/triage_report.md),Bash(gh api *),Bash(gh pr list *),Bash(python3 .claude/skills/triage-issue/assets/post_linear_comment.py *),Bash(rm -f /tmp/triage_report.md)" + --max-turns 20 --allowedTools "Write(triage_report.md),Bash(gh api *),Bash(gh pr list *),Bash(python3 .claude/skills/triage-issue/assets/post_linear_comment.py *),Bash(rm -f triage_report.md)" From 4afd2f3e2cb127ce73ac080d2d01ae8c346f9c50 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:42:18 +0100 Subject: [PATCH 3/4] format --- .claude/skills/triage-issue/SKILL.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.claude/skills/triage-issue/SKILL.md b/.claude/skills/triage-issue/SKILL.md index 05b428d69fa8..cce541d5af67 100644 --- a/.claude/skills/triage-issue/SKILL.md +++ b/.claude/skills/triage-issue/SKILL.md @@ -120,7 +120,6 @@ If the issue is complex or the fix is unclear, skip this section and instead not The script reads `LINEAR_CLIENT_ID` and `LINEAR_CLIENT_SECRET` from environment variables (set from GitHub Actions secrets), obtains an OAuth token, checks for duplicate triage comments, and posts the comment. 1. **Write the report body to a file** using the Write tool (not Bash). This keeps markdown completely out of shell. - - **In CI:** Write to `triage_report.md` in the repository root. The CI sandbox only allows writes inside the working directory; `/tmp` and Bash output redirection are blocked. - **Locally:** You may use `/tmp/triage_report.md` or `triage_report.md` in the repo root. From 481dd5c7ea63022abe7e703585b2158b22a22c09 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:44:27 +0100 Subject: [PATCH 4/4] change script --- .../triage-issue/assets/post_linear_comment.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.claude/skills/triage-issue/assets/post_linear_comment.py b/.claude/skills/triage-issue/assets/post_linear_comment.py index ee7f84464f0c..6d17d26f03ec 100644 --- a/.claude/skills/triage-issue/assets/post_linear_comment.py +++ b/.claude/skills/triage-issue/assets/post_linear_comment.py @@ -2,7 +2,13 @@ TIMEOUT_SECONDS = 30 IDENTIFIER_PATTERN = re.compile(r"^[A-Z]+-\d+$") -ALLOWED_REPORT_DIR = "/tmp/" +# /tmp/ is allowed for local runs; repo cwd is required in CI (sandbox only allows writes in working dir) +ALLOWED_REPORT_PREFIXES = ("/tmp/", os.path.abspath(os.getcwd()) + os.sep) + + +def _report_path_allowed(path: str) -> bool: + abs_path = os.path.abspath(path) + return any(abs_path.startswith(p) for p in ALLOWED_REPORT_PREFIXES) def graphql(token, query, variables=None): @@ -32,8 +38,10 @@ def graphql(token, query, variables=None): print(f"Invalid identifier format: {identifier}") sys.exit(1) -if not os.path.abspath(report_path).startswith(ALLOWED_REPORT_DIR): - print(f"Report path must be under {ALLOWED_REPORT_DIR}") +if not _report_path_allowed(report_path): + print( + f"Report path must be under /tmp/ or under current working directory ({os.getcwd()})" + ) sys.exit(1) client_id = os.environ["LINEAR_CLIENT_ID"]