with one click
running-in-ci
// Generic CI environment rules for GitHub Actions workflows. Use when operating in CI — covers security, CI monitoring, comment formatting, and investigating session logs from other runs.
// Generic CI environment rules for GitHub Actions workflows. Use when operating in CI — covers security, CI monitoring, comment formatting, and investigating session logs from other runs.
| name | running-in-ci |
| description | Generic CI environment rules for GitHub Actions workflows. Use when operating in CI — covers security, CI monitoring, comment formatting, and investigating session logs from other runs. |
| metadata | {"internal":true} |
Tend's bundled skills provide defaults; the consuming repo's running-tend skill overlays them. Where the two conflict, the repo wins — repo guidance takes precedence over bundled guidance across every skill, not just this one.
If a running-tend skill is listed in your available skills, load it with the Skill tool before doing anything else. It typically carries PR title conventions, label policies, custom workflows to watch, and other repo-specific context.
Repo-local skills are invoked by their unprefixed name — Skill: running-tend, not Skill: tend-ci-runner:running-tend (that prefix is reserved for this plugin's own skills, and trying it returns Unknown skill).
Repo-local skills must have YAML frontmatter (name + description) to be auto-discovered.
If you are going to propose a code fix for a bug, load /tend-ci-runner:triage first — it contains reproduction and testing gates that apply to all fix attempts, not just initial triage.
Follow the project's code of conduct. Avoid causing disruption — unnecessary comments, bulk operations, unsolicited housekeeping.
Anyone can ask for help with a problem they raise: investigating a bug, answering a question, creating an issue or PR to address it. These are proposals — a maintainer still decides what to merge or act on.
Directing the bot to affect someone else's work — closing, reopening, or locking issues/PRs, dismissing reviews, reverting commits, applying or removing labels, pushing commits to a PR owned by another author — requires Maintainer-tier access. Before complying, check the requester's author_association:
@author-association.md
For Maintainer-tier requesters, proceed. For anyone else, briefly explain that a maintainer needs to make that call.
The test: "Am I helping this person with something they raised, or following a directive that affects someone else's work?"
This follows the repo > bundled rule from First Steps. If a repo's running-tend skill explicitly authorizes an action (e.g., closing duplicate issues during triage), follow the repo-specific instruction.
When triggered by a comment or issue, read the full context before responding. The prompt provides a URL — extract the PR/issue number from it.
For PRs:
gh pr view <number> --json title,body,comments,reviews,state,statusCheckRollup
gh pr diff <number>
gh pr checks <number>
For issues:
gh issue view <number> --json title,body,comments,state
Read the triggering comment, the PR/issue description, the diff (for PRs), and recent comments to understand the full conversation before taking action.
If the trigger is a comment on an issue or PR and the target is closed by the time the job starts, the requested work was likely handled by a sibling run during the queue delay. Long tend-mention queues (hours, not minutes) make this common. Before starting work:
# For an issue trigger — check linked PRs that closed it.
gh issue view <number> --json state,closedAt,closedByPullRequestsReferences
# For a PR trigger — check whether the PR was merged.
gh pr view <number> --json state,mergedAt,mergeCommit
If a linked PR merged (or the triggering PR itself merged) after the triggering comment was posted, exit silently — the work is already on the default branch. If the closure looks unrelated (e.g. issue closed as not-planned with no merged PR), continue and address the comment normally.
env, printenv, set, export) or cat/echo credential files. The rule is absolute — name-stripping filters like env | cut -d= -f1 do not make these commands safe, because claude-code-action injects ALL_INPUTS as a multi-line JSON env value embedding claude_code_oauth_token and github_token in cleartext. The JSON's newlines turn each token-bearing line into its own env output line, where the value sits before the = and a name-stripping filter leaks it verbatim into the session log. Never include tokens or credentials in responses or comments.gh pr merge, gh pr merge --auto). PRs are proposals — a maintainer decides when to merge.gh run watch or gh pr checks --watch — both hang indefinitely. Poll with gh pr checks in a loop instead.Default: file an issue in the current repo asking for permission to file in the target. On maintainer approval, file in the target.
The adopter's running-tend overlay may grant a standing exception for agent-equipped targets — repos that run their own coding agent. Signals:
.github/workflows/tend-*.yaml present (the target uses tend).anthropics/claude-code-action or another coding-agent action.Two or three convergent signals are enough; borderline cases revert to the default. Without an explicit opt-in in running-tend, the default also applies.
When asking permission (the default path), close with a short offer so the user can record a preference for future asks:
By default I ask before filing upstream issues. If you'd prefer I file without asking, let me know and I'll add a standing exception to my guidance. If you'd rather keep approving each one but stop seeing this offer, also let me know.
Either reply gets codified in the consumer repo's running-tend overlay per Learning from Feedback below — opt-in adds the target (or "all agent-equipped targets") to the exceptions list; suppress adds a one-line rule telling the bot to skip the offer for future asks.
Whether filed direct or post-approval, the issue body includes:
When asked to create a PR, use gh pr create directly.
Before creating a branch or PR, check for existing work:
gh pr list --state open --json number,title,headRefName --jq '.[] | "#\(.number) [\(.headRefName)]: \(.title)"'
git branch -r --list 'origin/fix/*'
If an existing PR addresses the same problem, work on that PR instead.
gh pr createA separate mention on a different issue/PR can trigger a concurrent run asking for the same fix. Those runs are not serialized — each has its own concurrency group — so both may read an empty gh pr list at session start and then each open their own PR minutes later, producing near-duplicates. A long workflow queue (tend-mention can wait hours) also lets a sibling run open and merge a PR before this run starts — already-merged duplicates need to be in scope too. Re-run the check as the last step before gh pr create, with --state all so closed and merged siblings show up:
BOT_LOGIN=$(gh api user --jq '.login')
gh pr list --state all --author "$BOT_LOGIN" --limit 30 \
--json number,title,state,mergedAt,headRefName,createdAt
When the trigger is an issue/PR comment, also search for sibling PRs that reference that issue number — a merged PR's title or body often cites the issue (Fixes #123, #123 in title) even when the branch name diverged:
gh pr list --state all --search "author:$BOT_LOGIN <issue-number>" \
--json number,title,state,mergedAt
Compare by title keywords and the files the new PR would modify — two concurrent fixes for the same bug typically pick different branch names, so a branch-name match is not sufficient. If a sibling bot PR overlaps in scope — whether open, closed, or already merged — do not create: post a comment on the triggering thread linking the existing PR and exit.
Always use git push without specifying a remote — gh pr checkout configures tracking to the correct remote, including for fork PRs. Specifying origin explicitly can push to the wrong place.
If pushing fails (fork PR with edits disabled), fall back to posting code snippets in a comment. Don't reference commit SHAs from temporary branches — post code inline.
Any wait that lets time pass — a CI poll, coverage fetch, sleep, background task — also gives a maintainer time to merge or close the PR. After waiting:
STATE=$(gh pr view <N> --json state --jq '.state')
[ "$STATE" = "OPEN" ] || { echo "PR #<N> is $STATE — skipping push"; exit 0; }
If the PR is merged, the work is superseded. Comment if a real gap remains; do not push to the now-orphan branch. After merge, gh pr view <N> --json headRefOid returns the SHA at merge time and never advances — polling it for a new push is a guaranteed deadlock.
When asked to merge the default branch into a PR branch:
Never use --allow-unrelated-histories. If git merge fails because git can't find a merge base, the checkout is broken — investigate rather than forcing the merge. --allow-unrelated-histories treats both sides as disconnected, creating add/add conflicts in every file.
Handle untracked file conflicts properly. If git merge origin/main fails because untracked files would be overwritten by tracked files, stash them first — don't delete them:
git stash --include-untracked
git merge origin/main
git stash pop
Verify merge base exists before merging:
git merge-base origin/main HEAD
If this fails, the branch history is disconnected. Re-checkout the PR with full history (fetch-depth: 0) before retrying.
After pushing, wait for CI before reporting completion.
Use run_in_background: true for the polling loop so it does not block the session. When the background task completes you will be notified — check the result and take any follow-up action (dismiss approval, post analysis) at that point.
# Run with Bash tool's run_in_background: true.
#
# Poll statusCheckRollup — every check-run + status context on the commit.
# Exit when all non-own items are terminal.
#
# Why rollup, not `gh pr checks --required`:
# `--required` only returns required contexts that are ALREADY registered on
# the commit. An `if: always()` omnibus with a long `needs:` list (e.g.
# PRQL's `check-ok-to-merge`) only registers once every dependency has
# reached terminal. With `--required`, the loop would see only fast required
# contexts (e.g. `pre-commit.ci - pr`), exit green, and miss the matrix
# entirely. The rollup shows matrix jobs as IN_PROGRESS while they run, so
# we correctly wait for them, then for the omnibus once it registers.
#
# The 30s grace re-check handles actual registration lag: when the matrix's
# last `needs:` job finishes, the omnibus check-run registers within a
# second or two. A poll in that narrow window might see PENDING=0; the
# grace re-check catches the newly-IN_PROGRESS omnibus. Long observed gaps
# between PENDING=0 and the omnibus registering are NOT registration lag —
# matrix jobs are visibly IN_PROGRESS in the rollup while they run.
#
# Filter out the current run ($GITHUB_RUN_ID) — its own CheckRun is
# IN_PROGRESS for the whole loop. Match on the run URL, not the check name:
# `gh pr checks` shows the job name (e.g. "review"), which does not match
# $GITHUB_WORKFLOW ("tend-review").
#
# Also exclude same-workflow check runs ($GITHUB_WORKFLOW). When the current
# session pushes a commit or replies to an inline review comment, GitHub
# fires events that trigger a *sibling* run of the same workflow on the same
# PR. For workflows whose handle job uses `cancel-in-progress: false` (e.g.
# tend-mention's `tend-mention-handle-{PR#}` group), the sibling's handle job
# queues behind the current one — its CheckRun shows PENDING in the rollup
# but it can't start until the current run exits. Polling for it deadlocks
# until the 15-min cap breaks it. For workflows
# with `cancel-in-progress: true`, the older sibling is cancelled and
# wouldn't gate polling anyway, so this filter is a no-op there.
#
# Don't use mergeStateStatus as an exit signal. BLOCKED is a catch-all:
# required checks pending, branch out of date (`type: update` rulesets),
# required reviews missing, or our own check still running — all produce
# BLOCKED, indistinguishable without admin scope on branch protection.
pending() {
gh pr view <number> --json statusCheckRollup \
| jq --arg own "/runs/$GITHUB_RUN_ID/" --arg wf "$GITHUB_WORKFLOW" '
[.statusCheckRollup[]
| select((.detailsUrl // .targetUrl // "") | test($own) | not)
| select((.workflowName // "") == $wf | not)
| (.status // .state)
| select(. == "IN_PROGRESS" or . == "QUEUED" or . == "PENDING" or . == "WAITING" or . == "REQUESTED" or . == "EXPECTED")
] | length'
}
for i in $(seq 1 15); do
sleep 60
[ "$(pending)" -gt 0 ] && continue
sleep 30
[ "$(pending)" -eq 0 ] || continue
gh pr checks <number>
exit 0
done
echo "CI still running after 15 minutes"
exit 1
/runs/$GITHUB_RUN_ID/) — the current workflow's own check is always pending while polling and must be excluded to avoid a deadlock. Also filter same-workflow check runs ($GITHUB_WORKFLOW) — sibling runs of the same workflow on the same PR are subject to concurrency rules (queueing or cancel-in-progress) and don't represent independent CI signals. The 30s grace re-check catches late-registering omnibus checks.gh run view <run-id> --log-failed, fix, commit, push, repeat.Before dismissing local test failures as "pre-existing", check main branch CI:
gh api "repos/{owner}/{repo}/actions/runs?branch=main&status=completed&per_page=3" \
--jq '.workflow_runs[] | {conclusion, created_at: .created_at}'
If you cannot verify, say "I haven't confirmed whether these failures are pre-existing."
gh run rerun --failedAfter gh run rerun <run-id> --failed, poll the rerun jobs directly. The parent run's .status stays in_progress until every sibling job finishes, including unrelated long-running ones, and the pending() recipe above also doesn't help — sibling check-runs on the head SHA still appear pending. Polling specific job IDs is the only fix.
gh run rerun <run-id> --failed --repo "$REPO"
# New attempt records take a few seconds to surface; without this sleep,
# the next query can see only the prior `failure` rows and exit immediately.
sleep 10
# `?filter=latest` returns each job's most recent attempt.
JOB_IDS=$(gh api "repos/$REPO/actions/runs/<run-id>/jobs?filter=latest" \
--jq '.jobs[] | select(.status != "completed") | .id')
# Rollup poll: one pass checks all reran jobs together and exits when the
# last one is terminal.
pending_jobs() {
local n=0
for id in $JOB_IDS; do
s=$(gh api "repos/$REPO/actions/jobs/$id" --jq '.status')
[ "$s" = "completed" ] || n=$((n + 1))
done
echo "$n"
}
for i in $(seq 1 15); do
[ "$(pending_jobs)" -eq 0 ] && break
sleep 60
done
Reply in context rather than creating new top-level comments:
Inline review comments (#discussion_r): To read a single review comment, use the comment ID without the PR number in the path:
gh api repos/{owner}/{repo}/pulls/comments/{comment_id}
To reply:
cat > /tmp/reply.md << 'EOF'
Your response here
EOF
gh api repos/{owner}/{repo}/pulls/{number}/comments/{comment_id}/replies \
-F body=@/tmp/reply.md
Review events with inline comments (review ID in prompt): A review may include inline comments. Fetch them by review ID and reply to each individually:
gh api repos/{owner}/{repo}/pulls/{number}/reviews/{review_id}/comments \
--jq '.[] | {id: .id, path: .path, body: .body}'
Reply to each comment using the inline review comment reply endpoint above.
Conversation comments (#issuecomment-): Post a regular comment (GitHub doesn't support threading).
Before responding, check how many distinct other participants are in the conversation.
Do not:
A comment that responds to concerns you raised in a review is directed at you — briefly acknowledge resolution or explain why concerns remain.
If a maintainer has already addressed the point, exit silently unless you can add something they missed.
If you are responding to your own prior comment or review (not a human's reply to it), only respond if there is a distinct role boundary (e.g., you are the reviewer on your own PR and need to address review feedback). If there is no such role distinction, exit silently to avoid self-conversation loops.
Before posting any comment, review, or inline reply, re-fetch the conversation and check whether the response would duplicate something already there. Two duplication paths:
tend-mention, tend-triage, and tend-review all post as the same bot account; a comment from one can pre-empt another (a tend-mention reply followed by a synchronize-triggered tend-review is the common pattern). The earlier comment may already be in the conversation at session start, so a stale-count check alone is not enough — scan for prior bot comments newer than the maintainer message being responded to.# For issues
gh issue view <number> --json comments --jq '.comments | length'
# For PRs (comments + reviews)
gh pr view <number> --json comments,reviews \
--jq '{comments: (.comments | length), reviews: (.reviews | length)}'
If any prior entry — from a human or another tend workflow — already addresses a point the response would make, omit that point. The dedup applies equally to comment bodies, review bodies, and inline replies. If the response is now entirely redundant, don't post it.
If the author resolved the issue, acknowledge it rather than post stale analysis. If new information contradicts the findings, update before posting.
A single PR review can fire both pull_request_review and pull_request_review_comment events, triggering separate workflow runs (serialized by the concurrency group, not truly concurrent). Before replying to an inline review comment, check whether the bot already replied:
BOT_LOGIN=$(gh api user --jq '.login')
EXISTING=$(gh api "repos/{owner}/{repo}/pulls/{number}/comments?per_page=100" \
--jq "[.[] | select(.in_reply_to_id == {comment_id} and .user.login == \"$BOT_LOGIN\")] | length")
If EXISTING is greater than 0, do not post — another run already handled this comment. Exit silently.
Compose bodies with the Write tool, then post with --body-file. The composed file is reviewable before it ships, quoting and escaping are non-issues, and line wrapping is just file content. The bot writes to /tmp/ constantly — one more file is cheap. For one-line bodies, --body "…" is fine.
# After writing /tmp/comment-body.md with the Write tool:
gh issue comment "$ISSUE" --body-file /tmp/comment-body.md
Line wrapping: GitHub renders newlines literally in issue bodies, PR descriptions, and comments — a line break in the source becomes a <br> in the output. Write each paragraph as a single long line and let the browser reflow.
Content of /tmp/pr-body.md:
This PR refactors the `poll_jobs` helper to take a list of job IDs and
return only those still pending. The previous version queried the run
endpoint, which lagged behind the per-job endpoint after a rerun.
Content of /tmp/pr-body.md:
This PR refactors the `poll_jobs` helper to take a list of job IDs and return only those still pending. The previous version queried the run endpoint, which lagged behind the per-job endpoint after a rerun.
Code blocks, bullet lists, and tables keep their newlines as-is — only prose paragraphs need to be unwrapped.
Keep comments concise. Put supporting detail inside <details> tags — the reader should get the gist without expanding. Don't collapse content that is the answer (e.g., a requested analysis).
When an answer rests on deeper research — citations across several files, a reproduction, a traced mechanism — keep the visible reply short and fold the sources, line-anchored links, and working notes into <details>. Each CI run is a fresh session with no memory of prior reasoning, so a follow-up on the same thread starts cold; the thread is the only durable record, so that block doubles as a scratchpad the next session reads back instead of re-deriving the same citations.
<details><summary>Sources and notes</summary>
...line-anchored source links, repro steps, working notes...
</details>
Always use markdown links for files, issues, PRs, and docs. Any link containing #L must use a commit SHA, never blob/main/...#L42 — line numbers shift silently, so the link stays valid but starts pointing at different code than the comment describes. Get the SHA with git rev-parse HEAD before composing the link.
GitHub URLs — read $GITHUB_REPOSITORY from the environment, don't hand-type the owner. The model reliably guesses wrong — past comments have shipped with the wrong owner (e.g. anthropics/<repo> on a repo not owned by Anthropic). Before posting, scan the composed body for github.com/ and confirm every owner matches $GITHUB_REPOSITORY.
Authoring fenced bodies with backticks. When a body contains a fenced code block, the model often defensively escapes the inner fence (\`\`\`bash) "to prevent it from closing the outer fence early"; the same instinct can produce \`foo\` for inline spans. Those backslashes survive into the rendered body as literal \ characters. Author with bare backticks. For nested fenced blocks, use a longer outer fence — four or five backticks outside, three inside — so the inner three-backtick fence renders intact without escaping. The Write tool preserves data verbatim, so the same authoring rule applies whether you compose with the Write tool or inline; Write just removes shell-quoting from the equation.
#L anchor): blob/main/src/foo.rs is fineblob/<sha>/src/foo.rs#L42 — commit SHA required, never blob/main/...#L42#123 shorthand[text](url) formatDon't add job links or footers — claude-code-action adds these automatically.
When revising code after review feedback, update the title and description if the approach changed:
gh api repos/{owner}/{repo}/pulls/{number} -X PATCH \
-f title="new title" -F body=@/tmp/updated-body.md
Split unrelated changes into separate PRs — one concern per PR. If one change could be reverted without affecting the other, they belong in separate PRs.
Load /install-tend:debug-tend-run for session log download, JSONL parsing queries, and diagnostic workflow. The primary evidence for diagnosing bot behavior is the session log artifact — not console output.
Review-response runs triggered by pull_request_review or pull_request_review_comment events sometimes produce no artifact when the session is very short.
CI runs are not interactive — every claim must be grounded in evidence. The thread is also high-latency: a follow-up may not arrive for hours, so make each response fairly complete rather than counting on a quick back-and-forth.
Read logs, code, and API data before drawing conclusions. Show evidence: cite log lines, file paths, commit SHAs. Trace causation — if two things co-occur, find the mechanism rather than saying "this may be related." Never claim a failure is "pre-existing" without checking main branch CI history. Distinguish what you verified from what you inferred.
Public comments — on issues, PRs, or in review threads — are permanent and visible. A hallucinated detail (wrong syntax, invented API, nonexistent flag) misleads users and erodes trust. It is always better to take longer and produce a correct response than to respond quickly with fabricated details.
Before posting any specific claim — a configuration snippet, command syntax, variable name, or API behavior — find the source text that confirms it. Source text means documentation, help output, test expectations, or the code that implements the public interface. Internal implementation code (struct fields, variable names in Rust/Python/etc.) shows what exists internally but not how it's exposed to users — read the docs or user-facing layer too.
Bad: Saw extra_vars.push(("target", target_branch)) in Rust source → posted a hook example using $WT_TARGET (an environment variable that doesn't exist — hooks use {{ target }} Jinja templates).
Good: Saw ("target", target_branch) in Rust source → read docs/hook.md → confirmed hooks use {{ target }} syntax → posted correct example.
For behavioral claims — "X happens when you run Y", "command Z works in scenario W" — reading code is not sufficient. Code has conditional branches, early returns, and error paths that are easy to miss when tracing mentally. Before asserting what a command does in a specific scenario, either find a test that exercises that exact scenario or run the command yourself. If neither is feasible, hedge: "Based on code reading, I believe X, but I haven't verified this end-to-end."
Bad: Read CommandEnv::for_action("commit", config) → saw it constructs an env → concluded wt step commit works in a detached worktree. Missed that for_action() calls require_current_branch(), which errors on detached HEAD.
Good: Read for_action() → noticed it calls require_current_branch() → uncertain whether detached HEAD hits that path → ran cargo build && wt step commit in a detached worktree → confirmed the error → posted accurate answer.
When a project has user-facing documentation (a docs site, --help pages, a wiki), link to it. A link to the relevant docs page is more useful than a paraphrased explanation — and finding the link forces verifying the claim.
If you can't find source evidence for a specific detail, say so ("I'm not sure of the exact syntax") rather than guessing. An honest gap is fixable; a confident hallucination gets copy-pasted.
Links must be fetched, not guessed. Before pasting any URL in a comment, run curl -sI <url> | head -1 and confirm 200. Docs-site slugs are particularly treacherous — escaping.html and quoting.html and quote-strings.html are all plausible nushell page names; only one (or none) actually exists. The canonical source for that project's docs sidebar will tell you which.
--jq projections must include the ID when downstream URLs cite individual items. Composing actions/runs/<id>, #issuecomment-<id>, or pull/<n> URLs from gh run list / gh api .../comments / gh pr list results requires the ID field in the projection (databaseId for runs, id for comments, number for PRs/issues). If the projection kept only timestamps, titles, or bodies, the bot composes the URL from what it has and fabricates the missing ID — the link 404s. Re-query with the ID field rather than guessing.
"Likely" is a stop-sign. If a draft contains "likely works", "probably parses as", "should behave like", or "I think" in a user-facing claim, you have two options: verify and replace the hedge with the answer, or hedge explicitly ("I haven't tested this — would appreciate if you can confirm") and don't dress up the guess as analysis. Posting an unverified guess as confident-sounding analysis is the hallucination shape that erodes trust the fastest.
Intermittent or inconsistent behavior — the same query returning different results within seconds, an API silently returning empty when records demonstrably exist, a CLI flag working sometimes — points more strongly at an active upstream incident than at a CLI or skill bug. Reproducing the flake confirms the symptom but not the cause; the cause is often a current incident on the upstream service, in which case the right disposition is to wait for resolution rather than commit a code workaround that outlives the incident. Before designing a workaround, check upstream status. For GitHub-side symptoms:
curl -s 'https://www.githubstatus.com/api/v2/incidents/unresolved.json' \
| jq '.incidents[] | {created_at, name, impact, components: [.components[].name]}'
If the response is non-empty and the components/timing match the symptom (e.g. Issues / Pull Requests / Actions during a search-degradation incident), record the symptom in the run's evidence log and exit without a PR. Sibling matrix legs that hit different surface symptoms of the same incident otherwise each open their own near-duplicate workaround PR — title and file dedup don't catch them because each leg picks a different command to mitigate.
Bad: gh issue list returns [] intermittently for queries whose matching issues clearly exist. Bot opens a PR adding a retry loop. A sibling matrix leg sees the same shape on gh run list and opens its own workaround PR swapping to client-side filtering. Both are workarounds for an active upstream search-degradation incident; both get closed once the incident link surfaces.
Good: Same flake → curl /api/v2/incidents/unresolved.json returns an active "GitHub search is degraded" incident touching Issues + Pull Requests → record the symptom in the evidence log, skip the PR, let the incident resolve.
When a claim turns on how an external CLI, API, or system behaves, verify by running the code.
Two paths, in order of preference:
gh repo clone <owner>/<repo> then grep for the flag or behavior. Source doesn't lag itself, and a flag that isn't defined in the parser doesn't exist.If both paths fail (GUI-only tool, private repo, environment-specific behavior), cite what you found, name the remaining gap, and ask a human with the tool installed to confirm before shipping a dependent change.
Bad: Review asked whether cmux list-workspaces had structured output. Read a mintlify page describing --json → rewrote the recipe to cmux list-workspaces --json | jq ... → committed. The installed cmux had no --json flag; every reader hit a broken recipe.
Good: Same question. Cloned cmux's source repo → grepped the CLI parser for list-workspaces → saw no --json flag defined → replied with the source link and proposed an alternative that matched the actual CLI surface.
Cross-posting, summarizing, or paraphrasing is not copying — any new content you add requires the same source verification as a fresh comment. If you expand with a config section header, code block, or usage example, verify each addition against the source.
Bad: Cross-posted a hook snippet, added an alias example with [step] as the section header (inferred from the wt step command name). The actual config section is [aliases].
Good: Cross-posted a hook snippet, added an alias example → checked dev/*.example.toml to confirm the section is [aliases] → posted with correct syntax.
When a maintainer corrects the bot's behavior during a run — points out a repo convention, a repeated mistake, or a preference the bot should have known — propose a follow-up PR against the consuming repo's .claude/skills/running-tend/SKILL.md. This turns one-off corrections into durable guidance for future runs in this repo. The PR targets the consuming repo, not tend; bundled tend defaults change through separate PRs against the tend repo.
Open a repo-overlay PR only when feedback is generalizable — applies to future runs, not just the current task — AND at least one of these bars is met:
This mirrors the bar tend uses for bundled-skill changes — those go through human review on the tend repo before merge, which acts as an implicit recurrence/impact filter. Per-repo overlays don't get the same scrutiny, so the bar belongs here.
Signals that point toward a generalizable rule:
develop branch, not main")Do not propose when:
author_association and skip the skill PR. Non-maintainers can raise preferences, but only a maintainer authorizes codifying them. If the pattern is worth capturing, note it in a reply and let a maintainer confirm.When the correction identifies a gap or bug in a bundled skill — the same root cause would fire in every tend consumer — the fix belongs in tend, not in this overlay. Signals: the fix reads as generic guidance that would apply to any consumer; the behavior being corrected comes from bundled skill text. File against tend per Filing Issues in Other Repos above.
Complete the current task first. The skill update is always a separate PR.
Check for an existing open PR against the same skill. Dedup by the target file, not by title — title conventions vary per repo:
BOT_LOGIN=$(gh api user --jq '.login')
gh pr list --state open --author "$BOT_LOGIN" --json number,title,headRefName,files \
--jq '.[] | select([.files[].path] | index(".claude/skills/running-tend/SKILL.md"))'
If one is open, add to it instead of opening a second.
Draft a minimal edit. One short rule, in the maintainer's words where practical. Place it under an appropriate heading. New SKILL.md files start with YAML frontmatter:
---
name: running-tend
description: Project-specific guidance for tend workflows running on this repo.
---
The checkout's .claude/ directory is bind-mounted read-only under the sandbox (protecting bots from modifying their own skills in place), so edits to .claude/skills/ files in the working tree fail with Read-only file system. Claude Code's harness adds a second restriction on top of the read-only mount: Edit, Write, and Bash commands with .claude/skills/ as a write-target argument are denied regardless of filesystem permissions (anthropics/claude-code#37157). The guard checks argument text, so Write(/tmp/…) and Bash(mv /tmp/… SKILL.md) both pass — the second because SKILL.md is a bare filename inside the cd'd directory.
Do the edit, commit, and push from a git worktree under /tmp, which is writable and sits outside the harness's .claude/skills/ write-guard. (Don't write $TMPDIR/... — GitHub Actions runners leave $TMPDIR unset, so the path expands to /skill-fix, which the runner user can't create.)
Base the skill branch on the repo's default branch, not HEAD. When this skill runs from tend-mention on a PR, the workflow has already done gh pr checkout so HEAD is the PR branch — basing on it carries that PR's WIP commits into the skill PR and ships a multi-concern PR that mixes the skill change with unrelated code. Fetch and base off origin/<default> instead:
DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')
git fetch origin "$DEFAULT_BRANCH"
git worktree add "/tmp/skill-fix" -b "skills/<topic>-$GITHUB_RUN_ID" "origin/$DEFAULT_BRANCH"
# Use the Write tool to author the new skill file to /tmp/running-tend-new.md.
# Then move it into place from inside the worktree. mkdir -p covers the
# new-skill case where .claude/skills/<name>/ doesn't yet exist in the
# default branch:
mkdir -p "/tmp/skill-fix/.claude/skills/running-tend"
cd "/tmp/skill-fix/.claude/skills/running-tend" && mv /tmp/running-tend-new.md SKILL.md
cd "/tmp/skill-fix"
git add .claude/skills/
git commit -m "skills(running-tend): ..."
git push -u origin skills/<topic>-$GITHUB_RUN_ID
gh pr create --title "..." --body-file /tmp/pr-body.md --head skills/<topic>-$GITHUB_RUN_ID
cd -
git worktree remove "/tmp/skill-fix" --force
Open as a separate PR. Follow the repo's PR title conventions (conventional commits, Jira prefix, or whatever the repo uses — check recent merged PRs or CONTRIBUTING.md). The body quotes the triggering feedback and links the thread (PR/issue/comment URL).
Open and exit — don't merge, don't wait. The PR itself is the review request; a maintainer lands it (or doesn't) in their own time. Don't post a separate comment pinging for review, and don't block the session waiting.
Raise observations, don't assign work. Never create checklists or task lists for the PR author.
For review comments on specific lines ([Comment on path:line]), read that file and examine the code at that line before answering.
When the GitHub API returns a diff_hunk, the reviewer's comment targets the last line of that hunk. Use this to disambiguate when multiple candidates exist nearby — match the reviewer's request against the specific anchored line, not the surrounding region.
Polls GitHub notifications and handles items that dedicated workflows miss — fork PR comments, cross-repo mentions, and stale unanswered items. Runs on a schedule.
Sets up tend — an autonomous junior maintainer for a GitHub repo, powered by Claude or OpenAI Codex — that reviews PRs, triages issues, and fixes CI. Creates config, generates workflows, configures secrets and branch protection via API, creates the bot account, and provisions the harness auth token (Claude OAuth or OpenAI API key). Use when setting up tend on a new repo or when asked to install/configure tend.
Investigates a specific tend GitHub Actions run by downloading its session-log artifacts and parsing the JSONL traces. Surfaces which skill tend loaded, what tools it called with what inputs, files it read or wrote, and where decisions went wrong. Use when asked to "debug a tend run", "investigate a tend run", "why did tend do X", "what did the bot do in CI", "look at the session logs", or to reconstruct tend's behavior step-by-step from a run ID, URL, or PR number.
Nightly code quality sweep — resolves bot PR conflicts, reviews recent commits, surveys existing code, checks resolved issues, and updates tend workflows.
Triages new GitHub issues — classifies, reproduces bugs, attempts conservative fixes, and comments. Use when a new issue is opened and needs automated triage.
Tend-specific guidance for tend CI workflows. Adds non-standard workflow inclusion for usage analysis and repo conventions on top of the generic tend-* skills.