diff --git a/.claude/skills/triage-issue/SKILL.md b/.claude/skills/triage-issue/SKILL.md index 763e1a6c2fbf..cce541d5af67 100644 --- a/.claude/skills/triage-issue/SKILL.md +++ b/.claude/skills/triage-issue/SKILL.md @@ -119,24 +119,28 @@ 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. - - Write the triage report to `/tmp/triage_report.md`. + 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. 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/.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"] 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)"