The old cores/SLOTS/2 left a full test suite on 2 of 10 cores even with the box idle. Reserve two cores for the agent sessions and split the rest across slots. Also retire the CPU framing on the subagent fan-out cap. The number stays 3, but the binding constraints are memory per worktree and the shared rate-limit window; subagents are mostly idle waiting on the API. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
16 KiB
Tracker Common - Shared Config & Setup
This document is referenced by the /work and /yolo skills. Do not invoke it directly.
Project config
Look for the tracker config in the current git repo root (git rev-parse --show-toplevel), trying these paths in order and using the first that exists:
.claude/tracker.json(preferred, generic name).claude/linear.json(legacy name, still supported)
If none exists, run First-time setup below, then continue. When creating the file in setup, prefer the generic .claude/tracker.json name.
Schema
GitHub example (tracker: github):
{
"tracker": "github",
"defaultBranch": "main",
"commitScope": "contracts",
"buildCommand": "pnpm test",
"contextFiles": ["AGENTS.md"],
"prReviewers": [],
"labels": []
}
Gitea example (tracker: gitea):
{
"tracker": "gitea",
"defaultBranch": "main",
"commitScope": "maestro",
"remoteHost": "gitea",
"remoteBaseUrl": "https://git.naps.pt",
"buildCommand": "cargo test",
"contextFiles": ["CLAUDE.md"],
"prReviewers": [],
"labels": []
}
Linear example (tracker: linear):
{
"tracker": "linear",
"org": "ern",
"team": "Ern",
"project": "Contracts v2",
"defaultBranch": "main",
"commitScope": "platform",
"buildCommand": "forge test",
"setupCommands": ["bun install"],
"contextFiles": ["docs/README.md"],
"prReviewers": [],
"labels": []
}
| Field | Required | Description |
|---|---|---|
tracker |
no | Where issues live: linear (default), gitea, or github. See Tracker backend below. |
linearMcp |
no | MCP server name when tracker: linear. Default linear-server. Set a per-workspace name (e.g. linear-acme) when a project has its own server. |
org |
linear only | Linear organization slug |
team |
linear only | Linear team name (for listing/creating issues) |
project |
no | Linear project name (narrows issue search). Ignored when tracker: gitea. |
defaultBranch |
no | Base branch, default main |
commitScope |
no | Conventional commit scope, e.g. platform -> feat(platform): ... |
buildCommand |
no | Command to verify the build. Run after implementation. |
setupCommands |
no | Commands to run inside a new worktree (install deps, etc.) |
contextFiles |
no | Files to read before coding (specs, architecture docs) |
prReviewers |
no | Reviewer usernames to request reviews from (/work only) |
labels |
no | Default issue labels for ad-hoc issues |
remoteHost |
no | github (default) or gitea. Selects which API /work uses for PR creation and review polling. /yolo is unaffected. Implied gitea when tracker: gitea, github when tracker: github. |
remoteBaseUrl |
no | Required when remoteHost: gitea or tracker: gitea. Base URL of the Gitea instance (e.g. https://git.naps.pt). |
The config file is named
.claude/tracker.json(preferred) or.claude/linear.json(legacy); either configures whichever trackertrackerselects.
Tracker backend
tracker selects where issues live. Every instruction below that refers to "the issue" applies to the configured backend; where the two differ (selection, status changes, branch naming) the gitea-specific steps are called out explicitly.
github: issues live in the repo's own GitHub issue tracker — no Linear MCP involved. The repo (owner/repo) is derived fromgit remote get-url origin; all issue and PR operations use theghCLI (must be authenticated —gh auth status).org/team/projectare ignored. GitHub has no workflow states, so WIP is signalled by assigning the issue to yourself (like gitea); the PR'sCloses #Ncloses the issue on merge.gitea: issues live in the repo's own Gitea issue tracker — no Linear MCP involved. The repo (owner/repo) is derived fromgit remote get-url origin; the API usesremoteBaseUrl+$GITEA_TOKEN(load viasource ~/.env.claudeif needed), exactly like the/workGitea PR variant.org/team/projectare ignored.linear(default when omitted, for compatibility): issues live in Linear, accessed via the MCP server named bylinearMcp(defaultlinear-server; a workspace with its own server sets its own name). Requiresorg+team. Tool calls use themcp__<linearMcp>__*prefix.
First-time setup
If no tracker config exists:
- Ask the user which
tracker(linear,gitea, orgithub). Forlinear, also askorg,team, and optionallyproject; forgitea, askremoteBaseUrl. - Ask which optional fields they want. Show the table above.
- Write the file to
.claude/tracker.json(preferred). Suggest they commit it or gitignore it depending on preference. - Continue with the task.
Remote host (/work only)
/work opens PRs and iterates on reviews. The forge it talks to is selected by remoteHost:
github(default): usesghforgh pr create,gh pr view, and the GitHub GraphQL review-thread API. No extra config needed. Requires an authenticatedgh(gh auth status).gitea: uses Gitea's REST API viacurl. RequiresremoteBaseUrl(e.g.https://git.naps.pt) and$GITEA_TOKENin the environment (typically loaded from~/.env.claude). See/work's "Gitea variant" section for the specific endpoints.
/yolo doesn't care about remoteHost — push and merge are plain git, which works against any remote.
Linear MCP auth (tracker: linear only)
Skip this section entirely when tracker: gitea.
The Linear MCP server (named by linearMcp, default linear-server) supports one org at a time. Before making Linear API calls, verify the current auth matches the configured org. If it doesn't (or if auth fails), tell the user to re-authenticate via mcp__<linearMcp>__authenticate and stop. Don't try to work around auth issues silently.
Task selection
Based on $ARGUMENTS. The steps differ by tracker.
tracker: linear
Linear issue ID provided (e.g. ERN-347)
- Fetch the issue via the configured Linear MCP (
get_issue). - Read the full description, acceptance criteria, and comments.
Ad-hoc task description provided (free text, not matching an issue ID pattern)
- Create a new Linear issue in the configured team (and project if set).
- Apply any configured default
labels. - Use the provided text as the issue title. If it's long, summarize for the title and use the full text as description.
No argument (auto-pick)
- List issues in the configured team/project that are unstarted (Backlog, Todo, Ready, or equivalent).
- Pick the highest-priority unblocked issue.
- If none found, tell the user and stop.
Then:
- Move the issue to "In Progress" immediately.
- Note the issue ID, title, and
gitBranchNamefor later use. - Use
gitBranchNamefrom the Linear response for branch naming (auto-links in Linear).
tracker: gitea
Set BASE=$remoteBaseUrl, REPO=<owner>/<repo> (from git remote get-url origin), and send Authorization: token $GITEA_TOKEN on every call.
Issue number provided (e.g. 23 or #23)
GET $BASE/api/v1/repos/$REPO/issues/23.- Read the title, body, and comments (
GET .../issues/23/comments).
Ad-hoc task description provided (free text, not a bare number)
- Create the issue:
curl -sS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \ "$BASE/api/v1/repos/$REPO/issues" \ -d "$(jq -nc --arg title "<title>" --arg body "<full text>" '{title:$title, body:$body}')" - Apply any configured default
labels(resolve label names to IDs viaGET .../labels, passlabels:[ids]). - Title from the text (summarize if long; full text → body). Save the returned
number.
No argument (auto-pick)
GET $BASE/api/v1/repos/$REPO/issues?state=open&type=issues&limit=50(thetype=issuesfilter excludes PRs).- Prefer an unassigned issue; if priority labels exist (e.g.
priority/*), pick the highest, else the oldest open one. - If none found, tell the user and stop.
Then:
- Gitea has no workflow states — signal WIP by assigning the issue to yourself:
Optionally add a
curl -sS -X PATCH -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \ "$BASE/api/v1/repos/$REPO/issues/$N" -d '{"assignees":["<your-gitea-username>"]}'status/in-progresslabel if one already exists in the repo. Don't invent label schemes. - Note the issue
numberand title; derive the branch name (see Worktree setup).
tracker: github
Uses the gh CLI against the origin repo (REPO=<owner>/<repo> from git remote get-url origin). Requires an authenticated gh — run gh auth status first; if it fails, tell the user to run gh auth login -h github.com and stop.
Issue number provided (e.g. 23 or #23)
gh issue view 23 --json number,title,body,comments.- Read the title, body, and comments.
Ad-hoc task description provided (free text, not a bare number)
- Create the issue:
Apply any configured default
gh issue create --title "<title>" --body "<full text>" [--label <l1> --label <l2>]labels(each as a--labelflag; only labels that already exist in the repo). - Title from the text (summarize if long; full text → body). Save the returned issue
number(parse from the URLghprints).
No argument (auto-pick)
gh issue list --state open --limit 50 --json number,title,labels,assignees.- Prefer an unassigned issue; if priority labels exist (e.g.
priority/*), pick the highest, else the oldest open one. - If none found, tell the user and stop.
Then:
- GitHub has no workflow states — signal WIP by assigning the issue to yourself:
Optionally add a
gh issue edit $N --add-assignee @mestatus/in-progresslabel if one already exists in the repo. Don't invent label schemes. - Note the issue
numberand title; derive the branch name (see Worktree setup).
Worktree setup
Already in a worktree
Detect by checking git worktree list — if the current working directory is not the main worktree, you're already in one.
If already in a worktree: stay here. Check out the issue branch if the current branch doesn't match.
Not in a worktree
- Create a worktree at
worktrees/<branch-name>relative to the repo root.- tracker: linear: use
gitBranchNamefrom Linear (auto-links in Linear). If unavailable, derive a concise name from the issue title. - tracker: gitea: derive
<issue-number>-<slug>from the issue title (lowercase, hyphenated, ~5 words), e.g.8-daemon-activity-state. - tracker: github: derive
<issue-number>-<slug>from the issue title (lowercase, hyphenated, ~5 words), e.g.12-onchainid-erc3643-fixture. - Do NOT include "claude" in branch names.
- tracker: linear: use
cdinto the worktree.- Run each command in
setupCommandsfrom the config. - Set kitty tab title (silently skip if kitty isn't available):
kitty @ set-tab-title "<repo>/<branch>" 2>/dev/null || true
Gather context
- Read all
contextFilesfrom the config. - Re-read the tracking issue description (with project context you'll understand it better now).
- Read
CLAUDE.md/AGENTS.mdat the repo root or.claude/if they exist, for project conventions. - Check recent git history:
git log --oneline -20to understand current patterns. - Locating the code to change — delegate when the area is unfamiliar or spread out. If the issue clearly points at one or two known files, just read them inline. But if you'd otherwise open many files to answer "where does X live / what calls Y / where are the similar cases", spawn the
cavecrew-investigatorsubagent instead (Agent tool,subagent_type: caveman:cavecrew-investigator). It returns a compressedfile:linemap, keeping the main thread's context lean for the implementation and review that follow. Skip it for small, obvious, single-file tasks — the spawn overhead isn't worth it there.
Implementation guidelines
- Work methodically through the requirements.
- Commit after each logical step using conventional commits:
- With scope if configured:
feat(scope): description - Without:
feat: description - Prefixes:
feat,fix,refactor,test,docs,chore
- With scope if configured:
- Follow existing code patterns. Match the style of surrounding code.
- Write tests appropriate to the project (match existing test patterns and coverage level).
- Never amend commits — always create new ones.
- Keep the tracking issue updated (Linear, Gitea, or GitHub, per
tracker) if scope changes significantly.
Local verification budget (shared machine)
Several autonomous sessions (/yolo, /nightshift, /work, /blitz) run on one box at once, each with subagents. If every one runs the full suite whenever it feels like it, the machine OOMs and all of them die. If instead everything gets pushed for CI to check, CI is clogged and feedback is slow. So: scoped checks locally, full suite once per push, heavy commands through the gate.
Tiers
| When | Run | How |
|---|---|---|
| Inner loop, after each change | typecheck + lint + tests for the touched module only | direct, no gate |
| Once, right before push/PR | the project's buildCommand (full suite) |
through gate.sh |
| Anything else | nothing | let CI do it |
Never run the full suite twice for the same push. Never run it "to be sure" after a green scoped run of the same code.
The gate
Any command that compiles the whole project or runs the whole suite goes through the machine-wide semaphore:
<skills-root>/tracker-common/scripts/gate.sh -- <buildCommand>
It bounds concurrency machine-wide (default nproc/4 slots), caps the command's memory and CPU via a systemd scope, and pins test/build parallelism env vars (CARGO_BUILD_JOBS, RUST_TEST_THREADS, VITEST_MAX_*, MAKEFLAGS, GOMAXPROCS, node heap) so the suite doesn't fan out to every core.
- Exit 75 = no slot or too little free RAM within the wait budget. It did not run. That is a normal outcome, not an error: push and let CI cover it, and say so in the commit/PR body. Do not retry in a loop, do not bypass the gate by running the command directly.
- Exit 137 = killed by the memory cap, NOT a test failure. Do not go hunting for a bug that isn't there. Re-run once with
AGENT_GATE_MEM_MAX=6Gif the suite genuinely needs more. gate.sh --statusshows slots busy and free RAM. Cheap; check it before deciding to run anything heavy.- If the file isn't executable on this install, prefix with
bash. - Knobs (env):
AGENT_GATE_SLOTS,AGENT_GATE_WAIT(default 600s),AGENT_GATE_MEM_MAX(3G),AGENT_GATE_MEM_FLOOR(2000MB),AGENT_GATE_JOBS.
Subagents
- Subagents never run the full suite, ever. They run scoped checks on what they touched. The session that dispatched them runs the full suite once, at the end.
- Cap concurrent subagents at 3 per session, 2 if their tasks build or test. The binding constraint is memory per worktree and the shared rate-limit window, not cores — subagents are mostly idle waiting on the API.
gate.sh --statusshowing no free slots is a signal to dispatch fewer, not to wait.
Pushing
- One push per finished unit of work. Not per commit, not per milestone.
- Don't push to trigger CI as a substitute for the local scoped checks — that is how CI gets clogged.
- When the gate was skipped (exit 75), the push is doing real verification work: arm the one-shot CI watcher so a red build surfaces.
Rules
- Never ask the user during autonomous work unless you hit a genuine blocker (architectural contradiction, missing credentials, ambiguous requirements that could go very wrong).
- If the build fails, fix it before pushing.
- Respect existing project conventions from CLAUDE.md, AGENTS.md, etc.