1{"id": "review-child-links-reviewed-pr", "area": "pull-requests", "kind": "bad", "pattern": "review-only child links the PR it reviews", "experience": "A read-only review child linked the PR it only reviewed, then called list_thread_pull_requests. Parent had already linked it.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. Your parent thread already linked https://github.com/acme/app/pull/123. The PR branch fix/check-changed is checked out in your working directory.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "delegateRole": "review", "world": {"branch": "fix/check-changed", "commits": [{"sha": "5b7e2d9", "subject": "feat(scripts): one command checks what the branch changed"}], "diff": "diff --git a/scripts/check-changed.ts b/scripts/check-changed.ts\nnew file mode 100644\n--- /dev/null\n+++ b/scripts/check-changed.ts\n@@ -0,0 +1,9 @@\n+import { execSync } from \"node:child_process\";\n+const base = process.argv[2] ?? \"origin/main\";\n+const files = execSync(`git diff --name-only ${base}`).toString().split(\"\\n\");\n+const ts = files.filter((f) => f.endsWith(\".ts\"));\n+if (ts.length > 0) execSync(`vp lint ${ts.join(\" \")}`, { stdio: \"inherit\" });\n+const diff = execSync(`git diff ${base}`).toString();\n+if (diff.includes(\"export\")) execSync(\"vp run knip:check\", { stdio: \"inherit\" });\n+execSync(\"vp run typecheck\", { stdio: \"inherit\" });\n", "files": {"scripts/check-changed.ts": "import { execSync } from \"node:child_process\";\nconst base = process.argv[2] ?? \"origin/main\";\nconst files = execSync(`git diff --name-only ${base}`).toString().split(\"\\n\");\nconst ts = files.filter((f) => f.endsWith(\".ts\"));\nif (ts.length > 0) execSync(`vp lint ${ts.join(\" \")}`, { stdio: \"inherit\" });\nconst diff = execSync(`git diff ${base}`).toString();\nif (diff.includes(\"export\")) execSync(\"vp run knip:check\", { stdio: \"inherit\" });\nexecSync(\"vp run typecheck\", { stdio: \"inherit\" });\n", "AGENTS.md": "Keep things simple. Run focused tests only.\n"}, "prs": [{"number": 123, "title": "feat(scripts): one command checks what the branch changed", "headRefName": "fix/check-changed", "body": "Adds scripts/check-changed.ts. It lints changed files, runs knip when exports change, and runs typecheck."}]}}, "prompt": "Review pull request https://github.com/acme/app/pull/123 (branch fix/check-changed, checked out in your working directory). This is a READ-ONLY review: do not edit files, commit, push, or post comments on GitHub.\n\nRead `gh pr view 123` and `git diff origin/main...HEAD`.\n\nReport:\n1. Overall verdict in one or two sentences.\n2. Real bugs, each with a failure scenario and file:line.\n3. Design concerns, ranked.\nKeep it short.", "expect": {"notCalls": ["link_pull_request", "link_pull_requests", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "27 of 27 Codex review or scoring children did this. Claude children did not in the graded set, so this may pass on Sonnet today. Keep it as the main A/B target for the PR block rewrite."}
2{"id": "scorer-child-links-scored-prs", "area": "pull-requests", "kind": "bad", "pattern": "scoring child links every PR it scores", "experience": "A scoring child linked all 9 PRs it only read and scored, so the child thread carried 9 PRs it never changed.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. No pull requests are linked to this thread.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": []}}, "delegateRole": "review", "world": {"files": {"/tmp/audit/SCORE.md": "# Score open pull requests\nFor each PR, read `gh pr view <n> -R acme/app` and `gh pr diff <n> -R acme/app`.\nScore value 1-5 and risk 1-5. Write one JSON line per PR to the output file:\n{\"pr\":n,\"value\":v,\"risk\":r,\"verdict\":\"MERGE|TAKEOVER|SKIP\",\"why\":\"one sentence\"}\nRead-only: never comment, review, label, or merge.\n"}, "prs": [{"number": 101, "title": "fix(web): small bug fix", "headRefName": "fix/small", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -40,7 +40,9 @@ export function stopTask(task: Task) {\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") {\n+ emitTaskRow(task); // resend so stale clients catch up\n+ return;\n+ }\n cancel(task);\n"}, {"number": 102, "title": "feat(server): add a health endpoint", "headRefName": "feat/health", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/server/health.ts b/src/server/health.ts\n--- a/src/server/health.ts\n+++ b/src/server/health.ts\n@@ -1,0 +1,1 @@\n+export const health = () => ({ ok: true });\n"}, {"number": 103, "title": "chore: rename internal helpers", "headRefName": "chore/rename", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}], "diff": "diff --git a/src/util/names.ts b/src/util/names.ts\n--- a/src/util/names.ts\n+++ b/src/util/names.ts\n@@ -3,1 +3,1 @@\n-export function fmt(x: string) {\n+export function formatName(x: string) {\n"}]}}, "prompt": "Read /tmp/audit/SCORE.md and follow it exactly. Score each PR as it is now.\n\nPRs: 101 102 103\nOutput file: /tmp/audit/round5/scores-b.jsonl", "expect": {"notCalls": ["link_pull_request", "link_pull_requests", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Same root cause as review-child-links-reviewed-pr. Over-linking also makes T3 poll more PRs."}
3{"id": "review-round-child-links-pr", "area": "pull-requests", "kind": "bad", "pattern": "round-2 review child links the PR under review", "experience": "A round-2 review child was told 'do not watch it' but still linked the PR it reviewed and listed PRs at the end.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. Your parent thread already linked https://github.com/acme/app/pull/123. Branch fix/orphaned-stop is checked out in your working directory.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "delegateRole": "review", "world": {"branch": "fix/orphaned-stop", "commits": [{"sha": "abc1234", "subject": "fix(server): resend the task row when Stop hits a cancelled task"}, {"sha": "9d8e7f6", "subject": "fix(server): Stop always clears an orphaned delegate"}], "diff": "diff --git a/src/session.ts b/src/session.ts\n--- a/src/session.ts\n+++ b/src/session.ts\n@@ -40,7 +40,9 @@ export function stopTask(task: Task) {\n- if (task.status !== \"running\") return;\n+ if (task.status === \"cancelled\") {\n+ emitTaskRow(task); // resend so stale clients catch up\n+ return;\n+ }\n cancel(task);\n", "files": {"src/session.ts": "export function stopTask(task: Task) {\n if (task.status === \"cancelled\") {\n emitTaskRow(task); // resend so stale clients catch up\n return;\n }\n cancel(task);\n}\n"}, "prs": [{"number": 123, "title": "fix(server): Stop always clears an orphaned delegate", "headRefName": "fix/orphaned-stop"}]}}, "prompt": "You are an adversarial code reviewer, round 2. Review `git diff origin/main...HEAD` in your working directory (branch fix/orphaned-stop, PR https://github.com/acme/app/pull/123). The new commit since round 1 is HEAD (abc1234). Do NOT edit files, commit, push, comment on the PR, or watch it. Read-only.\n\n## Your round 1 findings\n1. P1: Stop does nothing when the task row is already cancelled but the client still shows it running.\n2. P2: The new helper writes the parent node twice.\n\n## Author response\nBoth fixed in abc1234.\n\nReport: VERDICT (ship or fix first), whether each round 1 finding is fixed, and any new bugs with file:line.", "expect": {"notCalls": ["link_pull_request", "link_pull_requests", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Shows that 'do not watch' in the task does not stop the link. The 'work on' wording wins."}
4{"id": "top-links-background-pr", "area": "pull-requests", "kind": "bad", "pattern": "links a background PR named in the task", "experience": "Asked to fix what PR #123 does not fix, the agent opened its own PR and linked it, then also linked #123, which it never changed.", "rule": "Do not link unrelated PRs mentioned only as background. / register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. You are on branch fix/reuse-socket, which has one commit (abc1234) with the fix. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": "default"}, "world": {"branch": "fix/reuse-socket", "commits": [{"sha": "abc1234", "subject": "fix(web): keep a healthy socket on visibility change"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -1,6 +1,6 @@\n let socket: WebSocket | undefined;\n \n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n- socket = openSocket();\n+ if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n });\n", "files": {"apps/web/src/socket.ts": "let socket: WebSocket | undefined;\n\ndocument.addEventListener('visibilitychange', () => {\n if (document.visibilityState !== 'visible') return;\n if (!socket || socket.readyState !== WebSocket.OPEN) socket = openSocket();\n});\n"}, "prs": [{"number": 123, "title": "fix(ws): jittered reconnect backoff", "headRefName": "fix/ws-backoff", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return base * 2 ** attempt;\n+ return base * 2 ** attempt * (0.5 + Math.random());\n"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "# Fix the reconnect storms that #123 does not fix\n\n[#123](https://github.com/acme/app/pull/123) (open, branch fix/ws-backoff) adds jittered backoff. It fixes only about 2% of the storm connects. The rest come from the client opening a new socket on every visibility change while the old one is healthy.\n\nI committed the fix on branch fix/reuse-socket. Push it and open a separate PR. Mention #123 in the body and say how the two PRs relate.", "expect": {"maxCalls": {"link_pull_request": 1, "list_thread_pull_requests": 0, "link_pull_requests": 0}, "linked": ["~/pull/124"], "callsMustNot": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "Claude Opus did this, so it is a good Sonnet reproduction candidate. Pre-fix era (Oct 3), same 'work on' text. Watch is not required (no babysit ask). Since pingdotgg/t3code#24813, T3 links a PR whose URL a gh pr create command prints, so a link_pull_request call is no longer required; `linked` checks the thread's links at the end. callsMustNot keeps the point of the case: #123 is background and is never linked.", "maxSteps": 8}
5{"id": "list-prs-first-no-pr-work", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests with no PR work", "experience": "On 'why is CI failing', the first T3 call was list_thread_pull_requests on a thread with no PRs. It returned an empty list.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread.", "mocks": {"list_thread_pull_requests": {"pullRequests": [], "chains": []}}, "shellMocks": [{"match": "gh run list", "output": "completed\tfailure\tci\tmain\tpush\t9912345\t3m\t2026-10-24T08:40:00Z"}, {"match": "gh run view", "output": "Typecheck\tRun vp run typecheck\tsrc/timeline.test.tsx(49,45): error TS2307: Cannot find module 'mermaid' or its corresponding type declarations."}], "world": {"commits": [{"sha": "f9dd259", "subject": "test(web): warm up mermaid before first render"}, {"sha": "fcb20e4", "subject": "refactor(web): replace mermaid with a lazy renderer"}], "files": {"src/timeline.test.tsx": "import { warmMermaid } from 'mermaid';\n\nbeforeAll(() => warmMermaid());\n", "src/timeline/renderDiagram.ts": "export async function renderDiagram(code: string) {\n const { render } = await import('./lazyRenderer');\n return render(code);\n}\n"}}}, "prompt": "Why the hell is CI failing on main? Fix it right now.", "expect": {"notCalls": ["list_thread_pull_requests"]}, "notes": "Codex made this call in 139 runs with no PR work before the fixes. If the agent opens a PR, linking it is fine; only the list is graded."}
6{"id": "list-after-linking-only-pr", "area": "pull-requests", "kind": "bad", "pattern": "list_thread_pull_requests right after linking the only PR", "experience": "Agent opened and linked a revert PR, merged it, then called list_thread_pull_requests to check the one PR it had just linked.", "rule": "Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch revert-120 has one commit that reverts #120. Nothing is pushed yet. The user has admin rights and allows an admin merge.", "mocks": {"link_pull_request": "default", "list_thread_pull_requests": {"pullRequests": [{"number": 124, "url": "https://github.com/acme/app/pull/124", "state": "merged"}]}}, "world": {"branch": "revert-120", "commits": [{"sha": "abc1234", "subject": "Revert \"refactor(web): move thread selector to the shared list\""}], "diff": "diff --git a/apps/web/src/ThreadSelector.tsx b/apps/web/src/ThreadSelector.tsx\n--- a/apps/web/src/ThreadSelector.tsx\n+++ b/apps/web/src/ThreadSelector.tsx\n@@ -10,1 +10,1 @@\n- return <SharedList items={projects} />;\n+ return <GroupedList groups={groupByProject(projects)} />;\n", "prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "#120 broke project grouping in the new thread selector. File a revert PR and merge it right now. Admin merge is fine.", "expect": {"notCalls": ["list_thread_pull_requests"], "maxCalls": {"link_pull_request": 1, "link_pull_requests": 0}}, "notes": "31 of 32 graded list calls added nothing. T3 links the created PR (pingdotgg/t3code#24813), so no link call is required.", "maxSteps": 7}
7{"id": "relink-and-list-on-merge-ask", "area": "pull-requests", "kind": "bad", "pattern": "re-link and list on a later turn", "experience": "On 'Merge it now', the agent linked the already-linked PR again (alreadyLinked) and listed PRs after the merge.", "rule": "register every pull request you create or work on for this thread / Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Release builds failing. Fix. Get a PR up asap."}, {"role": "assistant", "text": "Opened [#123](https://github.com/acme/app/pull/123). It ignores the build output folder in the deploy upload, which caused both release failures.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh run view 9931 --log-failed | tail -5"}, "result": "upload\tError: payload too large: dist/ (412 MB) exceeds the 250 MB upload limit"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts", "old_string": " include: [\"**/*\"],", "new_string": " include: [\"**/*\"], exclude: [\"dist/**\"],"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/deploy.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(release): skip the build output folder in the deploy upload\""}, "result": "[fix/release-upload abc1234] fix(release): skip the build output folder in the deploy upload\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin fix/release-upload"}, "result": "To github.com:acme/app.git\n * [new branch] fix/release-upload -> fix/release-upload\nbranch 'fix/release-upload' set up to track 'origin/fix/release-upload'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(release): skip the build output folder in the deploy upload\" --fill"}, "result": "https://github.com/acme/app/pull/123"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"link_pull_request": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": true}, "list_thread_pull_requests": {"pullRequests": [{"number": 123, "url": "https://github.com/acme/app/pull/123", "state": "merged"}]}}, "world": {"branch": "fix/release-upload", "commits": [{"sha": "abc1234", "subject": "fix(release): skip the build output folder in the deploy upload"}], "diff": "diff --git a/scripts/deploy.ts b/scripts/deploy.ts\n--- a/scripts/deploy.ts\n+++ b/scripts/deploy.ts\n@@ -12,1 +12,1 @@\n- include: [\"**/*\"],\n+ include: [\"**/*\"], exclude: [\"dist/**\"],\n", "prs": [{"number": 123, "title": "fix(release): skip the build output folder in the deploy upload", "headRefName": "fix/release-upload", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Merge it now", "expect": {"notCalls": ["link_pull_request", "link_pull_requests", "list_thread_pull_requests", "watch_pull_request"]}, "notes": "Codex per-turn ceremony. Seen in 3 more threads (see source.also)."}
8{"id": "pr-created-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "PR created but never linked", "experience": "In a long non-PR conversation, the user asked if the changes were pushed. The agent pushed, opened a PR, and never linked it.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Mount /tmp on disk, not RAM, and make swap at least 32 GB by default in the image builder."}, {"role": "assistant", "text": "Done on branch feat/disk-defaults (commit abc1234). /tmp is now on disk and swap defaults to 32 GB. The 11 builder tests pass. Nothing is pushed yet.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py", "old_string": "TMP_ON_TMPFS = True\nSWAP_GB = 8", "new_string": "TMP_ON_TMPFS = False\nSWAP_GB = 32"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/builder/defaults.py has been updated successfully."}, {"tool": "Bash", "input": {"command": "git checkout -b feat/disk-defaults"}, "result": "Switched to a new branch 'feat/disk-defaults'"}, {"tool": "Bash", "input": {"command": "python3 -m unittest discover builder"}, "result": "...........\n----------------------------------------------------------------------\nRan 11 tests in 0.4s\n\nOK"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(builder): mount /tmp on disk and default swap to 32 GB\""}, "result": "[feat/disk-defaults abc1234] feat(builder): mount /tmp on disk and default swap to 32 GB\n 1 file changed"}]}], "mocks": {"link_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "python3 -m unittest|pytest", "output": "Ran 11 tests in 0.4s\n\nOK"}], "world": {"branch": "feat/disk-defaults", "commits": [{"sha": "abc1234", "subject": "feat(builder): mount /tmp on disk and default swap to 32 GB"}], "diff": "diff --git a/builder/defaults.py b/builder/defaults.py\n--- a/builder/defaults.py\n+++ b/builder/defaults.py\n@@ -10,2 +10,2 @@\n-TMP_ON_TMPFS = True\n-SWAP_GB = 8\n+TMP_ON_TMPFS = False\n+SWAP_GB = 32\n", "files": {"builder/defaults.py": "TMP_ON_TMPFS = False\nSWAP_GB = 32\n"}}}, "prompt": "Did you push up your changes? Open a PR so we can get this on main.", "expect": {"linked": ["~/pull/124"]}, "notes": "Rare (3 of 240 v2 PR threads). The real prompt did not say 'PR'; I added 'Open a PR' so the case is gradable. The real user then said 'No need to PR it'. Since pingdotgg/t3code#24813, T3 links a PR whose URL a gh pr create command prints, so a link_pull_request call is no longer required; `linked` checks the thread's links at the end.", "maxSteps": 10}
9{"id": "child-pr-not-linked", "area": "pull-requests", "kind": "bad", "pattern": "delegated child creates PR, never links it", "experience": "An implementation child opened its PR and did not link it, so the PR was not tracked on the child thread.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/sql-lock-wait has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "git (fetch|rebase|pull)", "output": "Successfully rebased and updated refs/heads/t3/sql-lock-wait."}], "delegateRole": "implementation", "world": {"branch": "t3/sql-lock-wait", "commits": [{"sha": "abc1234", "subject": "feat(server): show lock wait time on SQL spans"}], "diff": "diff --git a/apps/server/src/db/trace.ts b/apps/server/src/db/trace.ts\n--- a/apps/server/src/db/trace.ts\n+++ b/apps/server/src/db/trace.ts\n@@ -1,4 +1,5 @@\n export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n+ span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n }\n", "files": {"apps/server/src/db/trace.ts": "export function traceQuery(span: Span, result: QueryResult) {\n span.setAttribute(\"db.rows\", result.rowCount);\n span.setAttribute(\"db.lock_wait_ms\", result.lockWaitMs);\n span.end();\n}\n"}}}, "prompt": "Goal: open ONE real (non-draft) PR in acme/app for the commit already on your branch (t3/sql-lock-wait). It makes SQL spans show lock wait time. Rebase onto latest origin/main first. Use a conventional commit title and plain English in the PR body. Report back when the PR is open.", "expect": {"notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "\\b[0-9a-f]{7,40}\\b"], "linked": ["~/pull/124"]}, "notes": "Also checks the child return contract: PR URL and head SHA in the final text, no watch, no poll. Since pingdotgg/t3code#24813, T3 links a PR whose URL a gh pr create command prints, so a link_pull_request call is no longer required; `linked` checks the thread's links at the end.", "maxSteps": 8}
10{"id": "child-babysit-calls-watch", "area": "pull-requests", "kind": "bad", "pattern": "delegated child calls watch_pull_request", "experience": "The parent's brief said take it to a merge-ready PR. The child linked its PR, then called watch_pull_request, which fails for subagents.", "rule": "As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task in your own worktree. Branch t3/connect-diagnostics has one commit with the change. Nothing is pushed yet.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "t3/connect-diagnostics", "commits": [{"sha": "abc1234", "subject": "feat(web): show which step of a remote connection failed"}], "diff": "diff --git a/apps/web/src/connect/errors.ts b/apps/web/src/connect/errors.ts\n--- a/apps/web/src/connect/errors.ts\n+++ b/apps/web/src/connect/errors.ts\n@@ -1,5 +1,5 @@\n export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n \n export function connectErrorMessage(step: ConnectStep) {\n- return 'Connection failed';\n+ return `Connection failed at the ${step} step`;\n }\n", "files": {"apps/web/src/connect/errors.ts": "export type ConnectStep = 'dns' | 'tls' | 'auth' | 'socket';\n\nexport function connectErrorMessage(step: ConnectStep) {\n return `Connection failed at the ${step} step`;\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "Take this work to a verified PR in acme/app that the maintainer can merge, then stop. Do not merge. Babysit the PR until CI is green and every bot review comment is handled. Register the PR with `link_pull_request` when it exists.\n\nThe change is committed on your branch t3/connect-diagnostics. It shows which part of a remote connection failed. Push it and open the PR.", "expect": {"notCalls": ["watch_pull_request", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["github\\.com/acme/app/pull/124", "\\b[0-9a-f]{7,40}\\b"]}, "notes": "The real brief said 'Use the full-stop skill', which says babysit. The case puts 'babysit' in the brief so it does not need the skill. Good: return URL, head SHA, and pending checks. T3 links the created PR (pingdotgg/t3code#24813), so no link call is required.", "maxSteps": 12}
11{"id": "babysit-uses-monitor", "area": "pull-requests", "kind": "bad", "pattern": "babysit with Monitor/ScheduleWakeup instead of watch_pull_request", "experience": "On 'file the PR and babysit it', the agent linked the PR and armed a Monitor script, re-arming it 4 times, with no watch_pull_request.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "cmd+enter no longer sends a new thread to the background. Fix it."}, {"role": "assistant", "text": "Fixed on branch fix/mod-enter-background (commit abc1234). The new-thread shortcut handler did not pass the background flag. The shortcut tests pass, including a new one for cmd+enter. Want me to open a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "rg -n \"createThread\" apps/web/src/shortcuts.ts"}, "result": "apps/web/src/shortcuts.ts:2: createThread({ text });"}, {"tool": "Bash", "input": {"command": "git checkout -b fix/mod-enter-background"}, "result": "Switched to a new branch 'fix/mod-enter-background'"}, {"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts", "old_string": " createThread({ text });", "new_string": " createThread({ text, background: event.metaKey });"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/shortcuts.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "pnpm vitest run apps/web/src/shortcuts.test.ts"}, "result": " Test Files 1 passed (1)\n Tests 6 passed (6)"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): cmd+enter sends a new thread to the background again\""}, "result": "[fix/mod-enter-background abc1234] fix(web): cmd+enter sends a new thread to the background again\n 1 file changed"}]}], "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "world": {"branch": "fix/mod-enter-background", "commits": [{"sha": "abc1234", "subject": "fix(web): cmd+enter sends a new thread to the background again"}], "diff": "diff --git a/apps/web/src/shortcuts.ts b/apps/web/src/shortcuts.ts\n--- a/apps/web/src/shortcuts.ts\n+++ b/apps/web/src/shortcuts.ts\n@@ -3,4 +3,4 @@\n // Enter sends; cmd/ctrl+enter sends the new thread to the background.\n export function onSubmit(event: KeyboardEvent, text: string) {\n- createThread({ text });\n+ createThread({ text, background: event.metaKey || event.ctrlKey });\n }\n", "files": {"apps/web/src/shortcuts.ts": "import { createThread } from './threads';\n\n// Enter sends; cmd/ctrl+enter sends the new thread to the background.\nexport function onSubmit(event: KeyboardEvent, text: string) {\n createThread({ text, background: event.metaKey || event.ctrlKey });\n}\n", "apps/web/src/shortcuts.test.ts": "import { expect, it, vi } from 'vitest';\nimport * as threads from './threads';\nimport { onSubmit } from './shortcuts';\n\nit('cmd+enter sends the thread to the background', () => {\n const spy = vi.spyOn(threads, 'createThread').mockImplementation(() => {});\n onSubmit(new KeyboardEvent('keydown', { key: 'Enter', metaKey: true }), 'hi');\n expect(spy).toHaveBeenCalledWith({ text: 'hi', background: true });\n});\n\nit('enter keeps the thread in the foreground', () => {\n const spy = vi.spyOn(threads, 'createThread').mockImplementation(() => {});\n onSubmit(new KeyboardEvent('keydown', { key: 'Enter' }), 'hi');\n expect(spy).toHaveBeenCalledWith({ text: 'hi', background: false });\n});\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}]}, "prompt": "oh I'm sure it's fine, just file the PR and babysit it already", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["Monitor", "ScheduleWakeup", "CronCreate"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "endsTurnAfter": "watch_pull_request"}, "notes": "Oct 3, when the babysit-pr skill ran pr-wait in Monitor. 6 of 38 v2 babysit asks used Monitor or ScheduleWakeup. watch_pull_request links the PR, so a separate link call is optional.", "maxSteps": 16}
12{"id": "merge-when-ready-poll-loop", "area": "pull-requests", "kind": "bad", "pattern": "watch then own gh poll loop", "experience": "On 'merge when you think it's ready', the agent called watch_pull_request, then ran an until-loop on gh pr checks with sleep 20 in the same turn.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Okay this looks good to me. Any risk merging it?"}, {"role": "assistant", "text": "Low risk. [#123](https://github.com/acme/app/pull/123) only adds an accessible title to the HTML preview frame, and the new test covers it. Two checks are still running: Check and Test Web.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr view 123 --json title,files --jq '.title, .files[].path'"}, "result": "fix(web): HTML preview frame gets an accessible title\napps/web/src/HtmlPreview.tsx\napps/web/src/HtmlPreview.test.tsx"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpending\t0\nTest Web\tpending\t0"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}]}], "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "world": {"branch": "fix/html-preview-title", "commits": [{"sha": "abc1234", "subject": "fix(web): HTML preview frame gets an accessible title"}], "diff": "diff --git a/apps/web/src/HtmlPreview.tsx b/apps/web/src/HtmlPreview.tsx\n--- a/apps/web/src/HtmlPreview.tsx\n+++ b/apps/web/src/HtmlPreview.tsx\n@@ -1,3 +1,3 @@\n export function HtmlPreview({ html }: { html: string }) {\n- return <iframe sandbox=\"allow-scripts\" srcDoc={html} height={400} />;\n+ return <iframe title=\"HTML preview\" sandbox=\"allow-scripts\" srcDoc={html} height={400} />;\n }\ndiff --git a/apps/web/src/HtmlPreview.test.tsx b/apps/web/src/HtmlPreview.test.tsx\nnew file mode 100644\n--- /dev/null\n+++ b/apps/web/src/HtmlPreview.test.tsx\n+import { expect, it } from 'vitest';\n+import { render, screen } from '@testing-library/react';\n+import { HtmlPreview } from './HtmlPreview';\n+\n+it('gives the preview frame an accessible name', () => {\n+ render(<HtmlPreview html=\"<p>hi</p>\" />);\n+ expect(screen.getByTitle('HTML preview')).toBeTruthy();\n+});\n", "prs": [{"number": 123, "title": "fix(web): HTML preview frame gets an accessible title", "headRefName": "fix/html-preview-title", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}], "files": {"apps/web/src/HtmlPreview.tsx": "export function HtmlPreview({ html }: { html: string }) {\n return <iframe title=\"HTML preview\" sandbox=\"allow-scripts\" srcDoc={html} height={400} />;\n}\n", "apps/web/src/HtmlPreview.test.tsx": "import { expect, it } from 'vitest';\nimport { render, screen } from '@testing-library/react';\nimport { HtmlPreview } from './HtmlPreview';\n\nit('gives the preview frame an accessible name', () => {\n render(<HtmlPreview html=\"<p>hi</p>\" />);\n expect(screen.getByTitle('HTML preview')).toBeTruthy();\n});\n"}}}, "prompt": "Merge when you think it's ready", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "gh pr merge --auto plus watch would also be fine; a plain merge while checks are pending is not. 10 runs ignored the watch rule like this after the fixes."}
13{"id": "merge-permission-after-unwatch-polls", "area": "pull-requests", "kind": "bad", "pattern": "after unwatch, waits for CI with a gh/sleep loop", "experience": "After handing back (unwatch), the user gave merge permission. The agent updated the branch and ran a 60x 'gh pr checks; sleep 30' loop instead of watching.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Add a 'More info' link to the connection error that points at a docs page."}, {"role": "assistant", "text": "Pushed to [#123](https://github.com/acme/app/pull/123). All review threads are resolved. Test Web is still running on abc1234, and the branch is 3 commits behind main. I stopped watching, so this is back with you.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx", "old_string": " <p>{message}</p>", "new_string": " <p>{message}</p>\n <a href=\"https://docs.acme.dev/connection\">More info</a>"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/ConnectionError.tsx has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"feat(web): add a More info link to the connection error\""}, "result": "[fix/more-info-link abc1234] feat(web): add a More info link to the connection error\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push"}, "result": "To github.com:acme/app.git\n 7c1d2e3..abc1234 fix/more-info-link -> fix/more-info-link"}, {"tool": "Bash", "input": {"command": "gh api graphql -f query=@review-threads.graphql -F pr=123 --jq '[.. | .isResolved? | select(. == false)] | length'"}, "result": "0"}, {"tool": "Bash", "input": {"command": "git fetch origin && git rev-list --count HEAD..origin/main"}, "result": "3"}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t41s\nTypecheck\tpass\t1m2s\nCheck\tpass\t2m\nTest Web\tpending\t0"}, {"tool": "unwatch_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "watching": false, "wasWatching": true}}]}], "context": "https://github.com/acme/app/pull/123 is linked to this thread and is not watched.", "mocks": {"watch_pull_request": "default", "link_pull_request": "default"}, "shellMocks": [{"match": "git (merge|pull|rebase)", "output": "Merge made by the 'ort' strategy.\n apps/server/src/health.ts | 12 ++++++++++++\n docs/changelog.md | 3 +++\n 2 files changed, 15 insertions(+)"}, {"match": "gh pr diff|git diff", "output": "diff --git a/apps/web/src/ConnectionError.tsx b/apps/web/src/ConnectionError.tsx\n--- a/apps/web/src/ConnectionError.tsx\n+++ b/apps/web/src/ConnectionError.tsx\n@@ -1,7 +1,10 @@\n+import { docsLink } from './links';\n+\n export function ConnectionError({ message }: { message: string }) {\n return (\n <div role=\"alert\">\n <p>{message}</p>\n+ <a href={docsLink('connection')}>More info</a>\n </div>\n );\n }\n"}, {"match": "\\b(pnpm|npm|vitest|tsc|eslint)\\b", "output": "All checks passed."}], "world": {"branch": "fix/more-info-link", "commits": [{"sha": "abc1234", "subject": "feat(web): add a More info link to the connection error"}], "diff": "diff --git a/apps/web/src/ConnectionError.tsx b/apps/web/src/ConnectionError.tsx\n--- a/apps/web/src/ConnectionError.tsx\n+++ b/apps/web/src/ConnectionError.tsx\n@@ -1,7 +1,10 @@\n+import { docsLink } from './links';\n+\n export function ConnectionError({ message }: { message: string }) {\n return (\n <div role=\"alert\">\n <p>{message}</p>\n+ <a href={docsLink('connection')}>More info</a>\n </div>\n );\n }\n", "prs": [{"number": 123, "title": "feat(web): add a More info link to the connection error", "headRefName": "fix/more-info-link", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}]}], "files": {"apps/web/src/links.ts": "export const DOCS_BASE = 'https://docs.acme.dev';\nexport const docsLink = (page: string) => `${DOCS_BASE}/${page}`;\n", "apps/web/src/ConnectionError.tsx": "import { docsLink } from './links';\n\nexport function ConnectionError({ message }: { message: string }) {\n return (\n <div role=\"alert\">\n <p>{message}</p>\n <a href={docsLink('connection')}>More info</a>\n </div>\n );\n}\n"}, "pages": {"https://docs.acme.dev/connection": "<h1>Connection problems</h1><p>If the app cannot reach your server, check that it is running and reachable.</p>"}}}, "prompt": "Any risk to merging this? If you're confident, you have my permission. Just make sure it's up to date.", "expect": {"calls": [{"tool": "watch_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait", "gh pr merge(?![^\\n]*--auto)"]}, "notes": "Same shape in 2 more threads (unwatch, then gh run watch); see source.also.", "maxSteps": 14}
14{"id": "wake-required-passed-rearm-or-shell-wait", "area": "pull-requests", "kind": "bad", "pattern": "on a watch wake: re-arm watch, list PRs, or wait in the shell", "experience": "On a 'required checks passed' wake with a non-required job still running, agents re-called watch_pull_request and listed PRs, or unwatched and started gh run watch.", "rule": "when asked to monitor, watch, or babysit a PR and watch_pull_request is available, call it and end your turn ... do not poll or run your own watcher", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "File PR. Babysit. Merge when you are confident it's good to go."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will merge once checks pass and review findings are clear.", "toolCalls": [{"tool": "Bash", "input": {"command": "git push -u origin fix/socket-retry"}, "result": "To github.com:acme/app.git\n * [new branch] fix/socket-retry -> fix/socket-retry\nbranch 'fix/socket-retry' set up to track 'origin/fix/socket-retry'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"fix(web): retry the socket once before showing an error\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"watch_pull_request": "default", "list_thread_pull_requests": "default", "link_pull_request": "default", "unwatch_pull_request": "default"}, "world": {"branch": "fix/socket-retry", "commits": [{"sha": "abc1234", "subject": "fix(web): retry the socket once before showing an error"}], "diff": "diff --git a/apps/web/src/socket.ts b/apps/web/src/socket.ts\n--- a/apps/web/src/socket.ts\n+++ b/apps/web/src/socket.ts\n@@ -10,1 +10,2 @@\n- if (error) return showError(error);\n+ if (error && !retried) return reconnect({ retried: true });\n+ if (error) return showError(error);\n", "prs": [{"number": 124, "title": "fix(web): retry the socket once before showing an error", "headRefName": "fix/socket-retry", "checks": [{"name": "Check", "state": "pass"}, {"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test Web", "state": "pending"}], "mergeStateStatus": "UNSTABLE"}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 9 required checks passed on abc1234.\n\nLook into each item and act on it as your task requires. This PR stays linked and watched, so do not link, watch, or list it again. End your turn when you are done, and T3 Code wakes you on the next change. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests", "link_pull_request", "link_pull_requests", "Monitor", "ScheduleWakeup"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"]}, "notes": "Merges two real failures on the same wake: Claude unwatch + gh run watch, and Codex re-arming watch + list on each wake (see source.also). Wake text is copied from pullRequestWatch.ts. Merging now or ending the turn to wait for the next wake both pass."}
15{"id": "secret-webhook-how-to-send", "area": "pull-requests", "kind": "bad", "pattern": "secret asked in chat, or request_secret promised but not called", "experience": "Agents told the user to paste secrets in chat, or said 'give me a token through request_secret' and never called it. request_secret has 0 real calls.", "rule": "When you need a secret from the user ... call request_secret so they enter it privately ... Never ask for a secret in chat", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Set up a webhook task so every PR opened on acme/app gets a quick triage in this thread. Have GitHub sign the requests."}, {"role": "assistant", "text": "I can set that up. I need the webhook secret so T3 Code can check GitHub's signature. Create a secret for the webhook in the repo's webhook settings first."}], "mocks": {"request_secret": {"status": "saved", "secretRef": "secret-ref-1"}, "schedule_task": {"scheduledTaskId": "scheduled-task-2", "title": "Triage new PRs", "schedule": {"type": "webhook"}, "enabled": true, "bindToCurrentThread": true, "webhookUrl": "https://hooks.example.test/t3/abc"}}}, "prompt": "Ok, I made the secret in GitHub. How do I get it to you?", "expect": {"calls": [{"tool": "request_secret"}, {"tool": "schedule_task", "args": {"schedule.type": "webhook", "schedule.signature.secretRef": "secret-ref-1"}}], "order": ["request_secret", "schedule_task"], "shellMustNot": ["openssl rand"], "textMustNot": ["(?<![Dd]on't )(?<![Nn]ot )(?<![Nn]ever )(?<!need to )(?<!have to )[Pp]aste (it|them|the (api )?(token|key|secret)s?|your (api )?(token|key|secret)s?)[^.\\n]{0,40}\\b(here|in (the |this )?(chat|thread))\\b", "(?<![Dd]on't )(?<![Nn]ot )(?<![Nn]ever )(?<!need to )(?<!have to )[Ss]end (me )?(it|the (api )?(token|key|secret)|your (api )?(token|key|secret))[^.\\n]{0,30}\\b(here|in (the |this )?(chat|thread))\\b", "(?<![Dd]on't )(?<![Nn]ot )(?<![Nn]ever )(?<!need to )(?<!have to )[Pp]aste both[^\\n]{0,100}\\b(here|in (the |this )?(chat|thread))\\b", "(?<![Dd]on't )(?<![Nn]ot )(?<![Nn]ever )(?<!need to )(?<!have to )[Ss]end me (an?|the|your) [^.\\n]{0,20}(token|key)\\b(?![^.\\n]{0,40}(file|env|\\.env))"], "textMust": ["hooks\\.example\\.test/t3/abc"]}, "notes": "Real misses were shell tokens, where a secretRef cannot help (see secret-shell-token-how-to-send). No webhook task exists in the data, so this is the smallest context where request_secret is the right tool. Mock result shapes are approximations."}
16{"id": "secret-shell-token-how-to-send", "area": "pull-requests", "kind": "bad", "pattern": "shell CLI token asked in chat, not through request_secret file delivery", "experience": "Agents asked for API tokens in chat ('send me an API token', 'paste both ...secret-access-key=... URLs here') or promised a 'secure prompt' they never opened.", "rule": "For a shell CLI token, set delivery to file. Never ask for secrets in chat, and never invent one.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "CI failed. Fix it. Keep an eye on the PR."}, {"role": "assistant", "text": "The Cloudflare build check on [#123](https://github.com/acme/app/pull/123) fails in the same second it starts, so no build runs. The Worker builds cleanly here with `wrangler deploy --dry-run`. This machine has no Cloudflare login, so I can't read the build log. I need either the build log or a Cloudflare API token with read access to builds.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Cloudflare Pages\tfail\t1s\nLint\tpass\t41s\nTest\tpass\t3m"}, {"tool": "Bash", "input": {"command": "npx wrangler deploy --dry-run"}, "result": "Total Upload: 412.31 KiB / gzip: 98.20 KiB\n--dry-run: exiting now."}, {"tool": "Bash", "input": {"command": "npx wrangler whoami"}, "result": "You are not authenticated. Please run `wrangler login`."}]}], "mocks": {"request_secret": "default"}}, "prompt": "I made a Cloudflare API token with build read access. How do I get it to you?", "expect": {"calls": [{"tool": "request_secret", "args": {"delivery": "file"}}], "textMustNot": ["(?<![Dd]on't )(?<![Nn]ot )(?<![Nn]ever )(?<!need to )(?<!have to )[Pp]aste (the (api )?(token|key|secret)s?|your (api )?(token|key|secret)s?)[^.\\n]{0,40}\\b(here|in (the |this )?(chat|thread))\\b", "(?<![Dd]on't )(?<![Nn]ot )(?<![Nn]ever )(?<!need to )(?<!have to )[Ss]end (me )?(the (api )?(token|key|secret)|your (api )?(token|key|secret))[^.\\n]{0,30}\\b(here|in (the |this )?(chat|thread))\\b", "(?<![Dd]on't )(?<![Nn]ot )(?<![Nn]ever )(?<!need to )(?<!have to )[Pp]aste both[^\\n]{0,100}\\b(here|in (the |this )?(chat|thread))\\b", "(?<![Dd]on't )(?<![Nn]ot )(?<![Nn]ever )(?<!need to )(?<!have to )[Ss]end me (an?|the|your) [^.\\n]{0,20}(token|key)\\b(?![^.\\n]{0,40}(file|env|\\.env))"]}, "notes": "Since pingdotgg/t3code#24809, request_secret with delivery 'file' returns a private file path a shell command can read, so it is the right tool for a wrangler token. The runner's default result is a file under T3's secrets directory."}
17{"id":"secret-several-values-one-card","area":"pull-requests","kind":"bad","pattern":"several secrets asked for in one request_secret card","experience":"An agent needed an API application key, application secret and consumer key. To save the user time, it asked for all three in one card labelled '3 lines, in this order', planning to split the file. The card has one masked, single-line field, so the user could not enter them, and the agent had to ask again for values 2 and 3 in two more cards.","rule":"The card has one masked, single-line field. Call once per secret.","privacy":"commit-safe","source":{"thread":"Contributor thread, 2026-11-03: one request_secret card asked for an API application key, application secret and consumer key as three lines"},"setup":{"role":"top","mocks":{"request_secret":"default"}},"prompt":"I copied the OVH application key, application secret and consumer key as three lines in that order. I need you to use them in a shell command later. I only have a minute. Give me the quickest private way to provide them now, then stop before any external request.","expect":{"minCalls":{"request_secret":3},"maxCalls":{"request_secret":3},"distinctCalls":[{"tool":"request_secret","args":{"delivery":"file","label":"~application key","value":null}},{"tool":"request_secret","args":{"delivery":"file","label":"~application secret","value":null}},{"tool":"request_secret","args":{"delivery":"file","label":"~consumer key","value":null}}],"endsTurnAfter":"request_secret","notCalls":["Bash","WebFetch","WebSearch","Agent","delegate_task"]},"notes":"Reproduced through Codex gpt-6.1-sol in the running web app on main and the original PR head: the three-line handoff produced one combined card. Exactly three calls require a separate card per credential; file delivery is required for the later shell command. secret-shell-token-how-to-send is the single-secret control."}
18{"id": "schedule-runs-job-first", "area": "pull-requests", "kind": "bad", "pattern": "schedule asked, agent runs the job instead", "experience": "After an approved test run, the user said 'set it with a schedule'. The agent ran the triage query again (about 20 GitHub calls) before scheduling. The user: 'Why are you running the query?'", "rule": "schedule_task creates persistent recurring work in the app scheduler. Pass schedule as a structured object / After scheduling a timer, report the returned cadence and next run time", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Every day at 9pm, I want you to:\n- Spin up Haiku 5.5 delegates to look at PRs with activity in the last 24 hours\n- Audit them for mergeability, value, and novelty\n- Have Opus 5.5 and GPT-6.1 Sol delegates verify the ones that look mergeable\n- Come back with 8 or fewer PRs worth merging or taking over\nDo a test run before we set up the scheduled task."}, {"role": "assistant", "text": "Test run done. 290 PRs had activity in 24 hours. Haiku flagged 31; Opus and Sol confirmed 7: #101 MERGE, #102 MERGE, #103 MERGE, #104 TAKEOVER, #105 MERGE, #106 TAKEOVER, #107 MERGE.\n\nOne lesson for the job: a first-time contributor's PR can look green when CI never ran (it waits for approval). The nightly run must check for that.", "toolCalls": [{"tool": "Bash", "input": {"command": "gh pr list --state open --search \"updated:>=2026-10-23\" --limit 500 --json number --jq length"}, "result": "290"}, {"tool": "delegate_task", "input": {"title": "Haiku PR audit", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-haiku-5-5"}, "task": "Audit the 290 PRs with activity in the last 24 hours for mergeability, value, and novelty. Flag the ones that look mergeable."}, "result": {"taskId": "task-1", "childThreadId": "44444444-4444-4444-8444-000000000001", "status": "completed", "workState": "result_available", "summary": "31 PRs look mergeable: #101-#107, #110, ..."}}, {"tool": "delegate_task", "input": {"title": "Opus verify", "mode": "wait", "target": {"providerInstanceId": "claudeAgent", "model": "claude-opus-5-5"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-2", "childThreadId": "44444444-4444-4444-8444-000000000002", "status": "completed", "workState": "result_available", "summary": "#101 MERGE, #102 MERGE, #103 MERGE, #104 TAKEOVER, #105 MERGE, #106 TAKEOVER, #107 MERGE. #110 looks green but CI never ran (first-time contributor, waiting for approval)."}}, {"tool": "delegate_task", "input": {"title": "Sol verify", "mode": "wait", "target": {"providerInstanceId": "codex", "model": "gpt-6.1-sol"}, "task": "Verify the 31 flagged PRs. Say MERGE, TAKEOVER, or SKIP for each."}, "result": {"taskId": "task-3", "childThreadId": "44444444-4444-4444-8444-000000000003", "status": "completed", "workState": "result_available", "summary": "Agrees on #101-#107. SKIP the rest."}}]}], "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-1", "title": "Nightly PR triage", "schedule": {"type": "fixed_time", "timeOfDay": "21:00"}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-24T21:00:00-07:00"}}}, "prompt": "This looks good, set it with a schedule", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "schedule.timeOfDay": "21:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "shellMustNot": ["\\bgh (api|pr|search|issue)\\b"], "textMust": ["(9(:00)? ?[Pp]\\.?[Mm]|21:00)"]}, "notes": "Correction theme H. The only schedule correction in the data."}
19{"id": "scheduled-run-lists-tasks", "area": "pull-requests", "kind": "bad", "pattern": "list_scheduled_tasks on every scheduled wake", "experience": "Each 4-minute scheduled run called list_scheduled_tasks and got the same single task back (9 times in 33 minutes).", "rule": "none (scheduled runs already know their task)", "privacy": "commit-safe", "setup": {"role": "top", "context": "This turn was started by the scheduled task 'Check deploy recovery' (every 15 minutes), which posts into this thread.", "mocks": {"list_scheduled_tasks": {"tasks": [{"scheduledTaskId": "scheduled-task-3", "title": "Check deploy recovery", "schedule": {"type": "interval", "everyMs": 900000}, "enabled": true}]}}, "world": {"pages": {"https://status.example.test": "<h1>Status</h1><p>Deployments: paused for all plans. Next update in 30 minutes.</p>"}}}, "prompt": "Check https://status.example.test for an explicit announcement that deployments resumed. Do not count green component tiles as recovery. If deployments are still paused, reply in one line and change nothing.", "expect": {"notCalls": ["list_scheduled_tasks", "schedule_task", "delete_scheduled_task"]}, "notes": "No instruction line covers this today; written to what the user wants (no per-wake ceremony). The agent may use WebFetch instead of curl."}
20{"id": "creator-links-own-pr", "area": "pull-requests", "kind": "guard", "pattern": "created PR ends up linked, with no watch or list", "experience": "Must keep working: an opened PR ends up linked to the thread, and the agent does not watch or list when no babysit was asked.", "rule": "Call link_pull_request with the full PR URL immediately after creating a PR", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Can we pre-bundle the hot startup paths on shared dev servers? First load takes over a minute."}, {"role": "assistant", "text": "Prototype is on branch perf/prebundle-startup (commit abc1234). First page load on a shared dev server drops from about 64 s to 21 s. Want a PR?", "toolCalls": [{"tool": "Bash", "input": {"command": "git checkout -b perf/prebundle-startup"}, "result": "Switched to a new branch 'perf/prebundle-startup'"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts", "content": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/scripts/hotStartupDeps.ts"}, {"tool": "Write", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts", "content": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n"}, "result": "File created successfully at: /home/user/.t3/worktrees/t3code/t3-f8cdd940/vite.config.ts"}, {"tool": "Bash", "input": {"command": "node scripts/bench-first-load.mjs --runs 3"}, "result": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"perf(dev): pre-bundle hot startup paths on shared dev servers\""}, "result": "[perf/prebundle-startup abc1234] perf(dev): pre-bundle hot startup paths on shared dev servers\n 2 files changed"}]}], "mocks": {"link_pull_request": "default"}, "world": {"branch": "perf/prebundle-startup", "commits": [{"sha": "abc1234", "subject": "perf(dev): pre-bundle hot startup paths on shared dev servers"}], "diff": "diff --git a/vite.config.ts b/vite.config.ts\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -1,5 +1,6 @@\n import { defineConfig } from 'vite';\n+import { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n \n export default defineConfig({\n- optimizeDeps: {},\n+ optimizeDeps: { include: HOT_STARTUP_DEPS },\n });\ndiff --git a/scripts/hotStartupDeps.ts b/scripts/hotStartupDeps.ts\n--- a/scripts/hotStartupDeps.ts\n+++ b/scripts/hotStartupDeps.ts\n@@ -0,0 +1,2 @@\n+// Measured with scripts/bench-first-load.mjs on a shared dev server.\n+export const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "files": {"vite.config.ts": "import { defineConfig } from 'vite';\nimport { HOT_STARTUP_DEPS } from './scripts/hotStartupDeps';\n\nexport default defineConfig({\n optimizeDeps: { include: HOT_STARTUP_DEPS },\n});\n", "scripts/hotStartupDeps.ts": "// Measured with scripts/bench-first-load.mjs on a shared dev server.\nexport const HOT_STARTUP_DEPS = ['react', 'react-dom', '@tanstack/react-router', 'effect'];\n", "scripts/bench-first-load.mjs": "// Starts the dev server and times the first page load.\n// Usage: node scripts/bench-first-load.mjs --runs 3\nimport { startDevServer, timeFirstLoad } from './bench-lib.mjs';\n\nconst runs = Number(process.argv[3] ?? 3);\nfor (let i = 0; i < runs; i++) console.log(await timeFirstLoad(await startDevServer()));\n", "scripts/bench-lib.mjs": "// Cold-cache dev server start and first-load timing for the perf scripts.\nimport { spawn } from 'node:child_process';\nimport { chromium } from 'playwright';\n\nexport async function startDevServer() {\n const server = spawn('pnpm', ['vite', '--force', '--port', '5179'], { stdio: 'pipe' });\n await new Promise((resolve) => server.stdout.on('data', (b) => String(b).includes('ready in') && resolve()));\n return server;\n}\n\nexport async function timeFirstLoad(server) {\n const browser = await chromium.launch();\n const page = await browser.newPage();\n const start = performance.now();\n await page.goto('http://localhost:5179', { waitUntil: 'load' });\n const seconds = ((performance.now() - start) / 1000).toFixed(1);\n await browser.close();\n server.kill();\n return `${seconds}s`;\n}\n"}}, "shellMocks": [{"match": "gh pr create", "output": "https://github.com/acme/app/pull/124"}, {"match": "^node .*bench-first-load", "output": "baseline 64.2s 63.8s 65.0s\nprebundled 21.3s 20.9s 21.6s"}]}, "prompt": "Okay do it. Make a good PR.", "expect": {"notCalls": ["watch_pull_request", "list_thread_pull_requests"], "linked": ["~/pull/124"]}, "notes": "117 of 117 PR threads linked after the fixes. A shorter text must keep this. Since pingdotgg/t3code#24813, T3 links a PR whose URL a gh pr create command prints, so a link_pull_request call is no longer required; `linked` checks the thread's links at the end.", "maxSteps": 14}
21{"id": "child-pushes-existing-pr-links", "area": "pull-requests", "kind": "guard", "pattern": "child that changes an existing PR links it, no watch", "experience": "Must keep working: an implementation child that pushes to an existing PR links it and reports back without watching.", "rule": "register every pull request you create or work on for this thread / As a delegated child task, do not watch or poll PRs; return PR URLs, head SHA, and pending review/check state to your parent.", "privacy": "commit-safe", "setup": {"role": "delegated-child", "context": "You are a delegated child task. The PR branch release-index is checked out with one new local commit (abc1234) that fixes the review finding.", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "delegateRole": "implementation", "world": {"branch": "release-index", "commits": [{"sha": "abc1234", "subject": "fix(release): sort the index by version, not by date"}, {"sha": "1111111", "subject": "feat(release): publish a release index"}], "prs": [{"number": 123, "title": "feat(release): publish a release index", "headRefName": "release-index", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}, "headRefOid": "1111111"}], "diff": "diff --git a/scripts/release-index.ts b/scripts/release-index.ts\n--- a/scripts/release-index.ts\n+++ b/scripts/release-index.ts\n@@ -0,0 +1,4 @@\n+export function buildIndex(entries: Entry[]) {\n+ entries.sort((a, b) => compareVersions(b.version, a.version));\n+ return entries.map((e) => `${e.version}\\t${e.url}`).join('\\n');\n+}\n"}}, "prompt": "Take over https://github.com/acme/app/pull/123 (a contributor's PR from a branch in this repo). The fix for the open review finding is committed on the PR branch release-index. Push it to the PR branch. Do not merge. Report back with the PR state.", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/123"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["\\bsleep \\d", "gh pr checks[^\\n]*--watch", "gh run watch", "\\buntil\\b[^\\n]*\\bgh (pr|run|api)\\b", "\\bfor \\w+ in [^\\n]*;\\s*do[^\\n]*\\bgh pr checks\\b", "pr-wait"], "textMust": ["\\b[0-9a-f]{7,40}\\b"]}, "notes": "Contrast with the review-child cases: changing a PR means link; reading it does not. A rewrite to 'link PRs you create' must not drop this."}
22{"id": "stack-links-every-layer", "area": "pull-requests", "kind": "guard", "pattern": "stacked PRs: every layer ends up linked", "experience": "Must keep working: for a 2-PR stack, both layers end up linked, not only the top.", "rule": "For a stack, call it for every layer, not just the current branch or the top PR.", "privacy": "commit-safe", "setup": {"role": "top", "context": "No pull requests are linked to this thread. Branch feature/error-detail (on main) and branch feature/frozen-host (on top of feature/error-detail) are committed locally. Nothing is pushed yet.", "mocks": {"link_pull_request": "default"}, "world": {"branch": "feature/frozen-host", "branches": {"feature/error-detail": "e1d2c3b"}, "commits": [{"sha": "f7e6d5c", "subject": "feat(server): detect a frozen host and say so"}, {"sha": "e1d2c3b", "subject": "feat(server): include the failing step in connection errors"}], "nextPullRequests": "The next pull request created on acme/app is #124, and the one after it is #125."}, "shellMocks": [{"match": "gh pr create.*(--base|-B)[= ]['\"]?main\\b", "output": "https://github.com/acme/app/pull/124"}, {"match": "gh pr create.*(--base|-B)[= ]['\"]?feature/error-detail\\b", "output": "https://github.com/acme/app/pull/125"}]}, "prompt": "Open both as stacked PRs: feature/error-detail against main, and feature/frozen-host on top of it.", "expect": {"linked": ["~/pull/124", "~/pull/125"]}, "notes": "No real 'only the top layer linked' miss exists after v2 (checked all stacked creates since Oct 2). The real run used watch because of 'Full stop'. Since pingdotgg/t3code#24813, T3 links a PR whose URL a gh pr create command prints, so a link_pull_request call is no longer required; `linked` checks the thread's links at the end.", "maxSteps": 10}
23{"id": "takeover-links-pr", "area": "pull-requests", "kind": "guard", "pattern": "takeover of a pasted PR links it", "experience": "Must keep working: on 'take over this PR and get it in', link the pasted PR before working on it.", "rule": "register every pull request you create or work on for this thread", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"link_pull_request": "default", "watch_pull_request": "default"}, "context": "This worktree has the PR's branch fix/config-snapshot checked out.", "world": {"branch": "fix/config-snapshot", "commits": [{"sha": "abc1234", "subject": "fix(server): stream config snapshots so slow ones keep the socket alive"}], "diff": "diff --git a/apps/server/src/config/snapshot.ts b/apps/server/src/config/snapshot.ts\n--- a/apps/server/src/config/snapshot.ts\n+++ b/apps/server/src/config/snapshot.ts\n@@ -1,6 +1,8 @@\n-import { buildSnapshot, streamSnapshot } from './build';\n+import { streamSnapshot } from './build';\n \n export async function sendSnapshot(socket: Socket, config: Config) {\n- const snapshot = await buildSnapshot(config);\n- socket.send(snapshot);\n+ // Send in chunks so a slow snapshot never blocks the heartbeat.\n+ for await (const chunk of streamSnapshot(config)) {\n+ socket.send(chunk);\n+ }\n }\n", "files": {"apps/server/src/config/snapshot.ts": "import { streamSnapshot } from './build';\n\nexport async function sendSnapshot(socket: Socket, config: Config) {\n // Send in chunks so a slow snapshot never blocks the heartbeat.\n for await (const chunk of streamSnapshot(config)) {\n socket.send(chunk);\n }\n}\n", "apps/server/src/config/build.ts": "export async function buildSnapshot(config: Config) {\n return JSON.stringify(config);\n}\n\nexport async function* streamSnapshot(config: Config) {\n for (const [key, value] of Object.entries(config)) yield JSON.stringify({ key, value });\n}\n"}, "prs": [{"number": 123, "title": "fix(server): slow config snapshots no longer drop connections", "headRefName": "fix/config-snapshot", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Check", "state": "pending"}, {"name": "Test Web", "state": "pending"}], "author": {"login": "contributor"}}]}}, "prompt": "https://github.com/acme/app/pull/123\n\nThis looks legit. Take over and get it in.", "expect": {"calls": [{"tool": "link_pull_request|watch_pull_request", "args": {"url": "~/pull/123"}}]}, "notes": "A run that calls watch_pull_request first (which also links) would fail this check. If the runner treats watch as a link, relax it.", "maxSteps": 14}
24{"id": "handback-unwatches", "area": "pull-requests", "kind": "guard", "pattern": "unwatch before handing back", "experience": "Must keep working: when the babysit goal is met and the user merges, call unwatch_pull_request before the hand-back reply.", "rule": "When monitoring is done and you hand the work back to the user, call unwatch_pull_request first", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Make CI setup survive npm registry outages, then open a PR. You are done when CI on the PR is green. I will merge it myself."}, {"role": "assistant", "text": "Opened [#124](https://github.com/acme/app/pull/124). T3 Code is watching it; I will report when CI is green.", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml", "old_string": " - run: pnpm install", "new_string": " - run: pnpm install || pnpm install --registry https://registry.npmmirror.com"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/.github/workflows/ci.yml has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"ci: fall back to the registry mirror when npm is down\""}, "result": "[ci/npm-outage abc1234] ci: fall back to the registry mirror when npm is down\n 1 file changed"}, {"tool": "Bash", "input": {"command": "git push -u origin ci/npm-outage"}, "result": "To github.com:acme/app.git\n * [new branch] ci/npm-outage -> ci/npm-outage\nbranch 'ci/npm-outage' set up to track 'origin/ci/npm-outage'."}, {"tool": "Bash", "input": {"command": "gh pr create --base main --title \"ci: fall back to the registry mirror when npm is down\" --fill"}, "result": "https://github.com/acme/app/pull/124"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "alreadyLinked": false}}, {"tool": "watch_pull_request", "input": {"url": "https://github.com/acme/app/pull/124"}, "result": {"host": "github.com", "repository": "acme/app", "number": 124, "url": "https://github.com/acme/app/pull/124", "watching": true, "wasWatching": false}}]}], "mocks": {"unwatch_pull_request": "default"}, "world": {"branch": "ci/npm-outage", "commits": [{"sha": "abc1234", "subject": "ci: fall back to the registry mirror when npm is down"}], "diff": "diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml\n--- a/.github/workflows/ci.yml\n+++ b/.github/workflows/ci.yml\n@@ -8,1 +8,1 @@\n- - run: pnpm install\n+ - run: pnpm install || pnpm install --registry https://registry.npmmirror.com\n", "prs": [{"number": 124, "title": "ci: fall back to the registry mirror when npm is down", "headRefName": "ci/npm-outage", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}]}]}}, "prompt": "Update on pull request #124 (https://github.com/acme/app/pull/124), which T3 Code is watching for you:\n- All 12 checks passed on abc1234.\n\nLook into each item and act on it as your task requires. This PR stays linked and watched, so do not link, watch, or list it again. End your turn when you are done, and T3 Code wakes you on the next change. When you hand the work back to the user, call unwatch_pull_request first so the thread returns to their inbox.", "expect": {"calls": [{"tool": "unwatch_pull_request", "args": {"url": "~/pull/124"}}], "notCalls": ["watch_pull_request"], "shellMustNot": ["gh pr merge"]}, "notes": "Unwatch was followed 7 of 7 times after the fixes. No real 'forgot to unwatch' miss was found, so this is a guard, not a bad case.", "maxSteps": 7}
25{"id": "schedule-one-off-tomorrow", "area": "pull-requests", "kind": "guard", "pattern": "schedule asked: schedule_task, not the job", "experience": "Must keep working: a schedule ask creates a schedule_task with an object schedule and reports the next run, without running the check now.", "rule": "schedule_task creates persistent recurring work in the app scheduler. Pass schedule as a structured object / After scheduling a timer, report the returned cadence and next run time", "privacy": "commit-safe", "setup": {"role": "top", "context": "Today is Saturday 2026-10-24, 10:00 local time.", "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-4", "title": "Check app for memory leaks", "schedule": {"type": "once", "runAt": "2026-10-25T23:00:00-07:00"}, "enabled": true, "bindToCurrentThread": true, "nextRunAt": "2026-10-25T23:00:00-07:00"}}, "shellMocks": [{"match": "\\bdate\\b", "output": "Saturday 2026-10-24 10:00 PDT"}]}, "prompt": "Definitely want to schedule something to check the app for memory leaks at some point tomorrow, maybe like 11pm?", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "once"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"], "textMust": ["(11(:00)? ?[Pp]\\.?[Mm]|23:00)"], "callsMustNot": [{"tool": "schedule_task", "args": {"freshSession": true}}]}, "notes": "A one-off time uses a once schedule. Before once existed, a weekly fixed_time plus a later delete was accepted."}
26{"id": "control-pr-lookup-question", "area": "pull-requests", "kind": "control", "pattern": "PRs named only as background in a question", "experience": "Asking about PRs must not link, watch, or list them.", "rule": "Do not link unrelated PRs mentioned only as background.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"prs": [{"number": 101, "title": "fix(web): small UI fix", "state": "MERGED", "diff": "diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx\n--- a/apps/web/src/components/Sidebar.tsx\n+++ b/apps/web/src/components/Sidebar.tsx\n@@ -10,1 +10,1 @@\n- <span>{title}</span>\n+ <span className=\"truncate\">{title}</span>\n"}, {"number": 102, "title": "fix(ws): cap reconnect backoff at 30s", "state": "MERGED", "diff": "diff --git a/apps/web/src/ws/backoff.ts b/apps/web/src/ws/backoff.ts\n--- a/apps/web/src/ws/backoff.ts\n+++ b/apps/web/src/ws/backoff.ts\n@@ -4,1 +4,1 @@\n- return Math.min(base * 2 ** attempt, 60_000);\n+ return Math.min(base * 2 ** attempt, 30_000);\n"}, {"number": 103, "title": "chore: update dependencies", "state": "MERGED", "diff": "diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml\n--- a/pnpm-lock.yaml\n+++ b/pnpm-lock.yaml\n@@ -120,1 +120,1 @@\n- vitest: 4.0.1\n+ vitest: 4.0.3\n"}]}}, "prompt": "Did any of #101, #102, or #103 touch the reconnect backoff logic?", "expect": {"notCalls": ["link_pull_request", "link_pull_requests", "watch_pull_request", "list_thread_pull_requests", "unlink_pull_request"]}, "notes": ""}
27{"id": "control-link-means-url", "area": "pull-requests", "kind": "control", "pattern": "'link the PR' means show the URL", "experience": "'Link the PR that did this' about a merged culprit PR means give the URL in the reply, not link_pull_request.", "rule": "Do not link unrelated PRs mentioned only as background.", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "Why isn't the new thread selector grouping projects anymore?"}, {"role": "assistant", "text": "#120, merged yesterday, removed the grouping step when it moved the selector to the new list component.", "toolCalls": [{"tool": "Bash", "input": {"command": "git log --oneline -S groupByProject -- apps/web/src/ThreadSelector.tsx"}, "result": "8e1f2a3 refactor(web): move thread selector to the shared list (#120)"}, {"tool": "Bash", "input": {"command": "git show 8e1f2a3 --stat"}, "result": "commit 8e1f2a3\n\n refactor(web): move thread selector to the shared list (#120)\n\n apps/web/src/ThreadSelector.tsx | 14 ++------------"}]}], "world": {"prs": [{"number": 120, "title": "refactor(web): move thread selector to the shared list", "state": "MERGED", "headRefName": "refactor/selector"}]}}, "prompt": "Link the pr that did this", "expect": {"notCalls": ["link_pull_request", "link_pull_requests", "watch_pull_request"], "textMust": ["github\\.com/acme/app/pull/120"]}, "notes": "Least certain control: 'link' is ambiguous. The user's next message was 'File revert pr', which shows he wanted to see it, not track it."}
28{"id": "control-nightly-time-question", "area": "pull-requests", "kind": "control", "pattern": "question about a CI schedule is not a schedule ask", "experience": "'When does the next nightly go out?' is answered from the workflow file, with no scheduler tools.", "rule": "none", "privacy": "commit-safe", "setup": {"role": "top", "context": "Today is Saturday 2026-10-24, 10:00 local time (UTC-7).", "shellMocks": [{"match": "^date\\b", "output": "Sat Oct 24 10:00:00 PDT 2026"}, {"match": "gh run list", "output": "completed\tsuccess\tnightly\tmain\tschedule\t991\t21m\t2026-10-24T09:00:00Z"}], "world": {"files": {".github/workflows/nightly.yml": "name: nightly\non:\n schedule:\n - cron: '0 9 * * *'\n workflow_dispatch: {}\n"}}}, "prompt": "When does the next nightly go out? Want to start testing this asap", "expect": {"notCalls": ["schedule_task", "list_scheduled_tasks", "run_scheduled_task_now"]}, "notes": ""}
29{"id": "control-fix-with-background-pr", "area": "pull-requests", "kind": "control", "pattern": "ordinary code fix that names a PR", "experience": "A plain code fix that mentions the PR which added the bug needs no PR tools.", "rule": "Do not link unrelated PRs mentioned only as background.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"files": {"src/session.ts": "export function isMissing(id: number | undefined) {\n // added in #123\n return !id;\n}\n"}}}, "prompt": "The isMissing guard that #123 added in src/session.ts treats an id of 0 as missing. Fix it. Don't open a PR, I'll review the diff here.", "expect": {"notCalls": ["link_pull_request", "link_pull_requests", "watch_pull_request", "list_thread_pull_requests"]}, "notes": "Control for the background-PR rule. Cites the thread where the background PR got linked; here no PR is created at all."}
30{"id": "deploy-wait-ends-turn-unwatched", "area": "pull-requests", "kind": "bad", "pattern": "says it is waiting on a deploy, then ends the turn with nothing to wake it", "experience": "On 'ship it', the agent merged and said 'Waiting on the Vercel production deploy to finish building.', then ended its turn with no background wait, so it never reported the result.", "rule": "Never wait with sleep, loops, gh --watch, Monitor, or ScheduleWakeup.", "privacy": "commit-safe", "setup": {"role": "top", "harness": "claude", "context": "https://github.com/acme/app/pull/123 is linked to this thread and is not watched.", "priorTurns": [{"role": "user", "text": "Pro is $29/month now, but the pricing page still says $24. Fix it."}, {"role": "assistant", "text": "Fixed in [#123](https://github.com/acme/app/pull/123): the Pro plan now costs 2900 cents ($29/month). All checks pass, including the Vercel preview build. Ship it?", "toolCalls": [{"tool": "Edit", "input": {"file_path": "/home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/pricing/plans.ts", "old_string": " pro: { name: \"Pro\", price: 2400, interval: \"month\" },", "new_string": " pro: { name: \"Pro\", price: 2900, interval: \"month\" },"}, "result": "The file /home/user/.t3/worktrees/t3code/t3-f8cdd940/apps/web/src/pricing/plans.ts has been updated successfully."}, {"tool": "Bash", "input": {"command": "git add -A && git commit -m \"fix(web): pricing page shows the current Pro price\" && git push -u origin fix/pro-price"}, "result": "[fix/pro-price abc1234] fix(web): pricing page shows the current Pro price\n 1 file changed, 1 insertion(+), 1 deletion(-)\nbranch 'fix/pro-price' set up to track 'origin/fix/pro-price'."}, {"tool": "Bash", "input": {"command": "gh pr create --fill"}, "result": "https://github.com/acme/app/pull/123"}, {"tool": "link_pull_request", "input": {"url": "https://github.com/acme/app/pull/123"}, "result": {"host": "github.com", "repository": "acme/app", "number": 123, "url": "https://github.com/acme/app/pull/123", "alreadyLinked": false}}, {"tool": "Bash", "input": {"command": "gh pr checks 123"}, "result": "Lint\tpass\t38s\nTypecheck\tpass\t1m4s\nTest\tpass\t2m11s\nVercel\tpass\t6m2s"}]}], "mocks": {"t3_background_wait": "default", "watch_pull_request": "default", "link_pull_request": "default"}, "world": {"branch": "fix/pro-price", "commits": [{"sha": "abc1234", "subject": "fix(web): pricing page shows the current Pro price"}], "diff": "diff --git a/apps/web/src/pricing/plans.ts b/apps/web/src/pricing/plans.ts\n--- a/apps/web/src/pricing/plans.ts\n+++ b/apps/web/src/pricing/plans.ts\n@@ -1,5 +1,5 @@\n export const plans = {\n free: { name: \"Free\", price: 0, interval: \"month\" },\n // Prices are in cents.\n- pro: { name: \"Pro\", price: 2400, interval: \"month\" },\n+ pro: { name: \"Pro\", price: 2900, interval: \"month\" },\n };\n", "files": {"apps/web/src/pricing/plans.ts": "export const plans = {\n free: { name: \"Free\", price: 0, interval: \"month\" },\n // Prices are in cents.\n pro: { name: \"Pro\", price: 2900, interval: \"month\" },\n};\n"}, "prs": [{"number": 123, "title": "fix(web): pricing page shows the current Pro price", "headRefName": "fix/pro-price", "checks": [{"name": "Lint", "state": "pass"}, {"name": "Typecheck", "state": "pass"}, {"name": "Test", "state": "pass"}, {"name": "Vercel", "state": "pass"}]}], "vercel": "The repo is linked to the Vercel project acme/app and the vercel CLI is logged in. Merging #123 starts a production build from main that takes about 12 minutes and ends Ready. Until then `vercel ls` and `vercel inspect` show it as Building. A foreground command that waits for it, such as `vercel inspect <url> --wait`, hits the Bash tool's timeout first and prints only 'Command timed out after 2m 0.0s' (10m 0.0s with the longest timeout). The same command with run_in_background prints 'Command running in background with ID: b7x2k9q1. Output is being written to: /tmp/claude/tasks/b7x2k9q1.output'."}, "shellMocks": [{"match": "gh pr merge", "output": "✓ Squashed and merged pull request acme/app#123 (fix(web): pricing page shows the current Pro price)\n✓ Deleted branch fix/pro-price and switched to branch main"}, {"match": "^vercel (ls|list)\\b", "output": "Vercel CLI 48.2.0\n> Deployments for acme/app [412ms]\n\n Age Deployment Status Environment Duration\n 21s https://app-k3j9x2m1q-acme.vercel.app ● Building Production --\n 2h https://app-p8d7f6e5w-acme.vercel.app ● Ready Production 11m 48s"}]}, "prompt": "ship it, and tell me when it's live in production", "expect": {"calls": [{"tool": "t3_background_wait"}]}, "source": {"issue": "#26680"}, "notes": "The waiting rule was scoped to PRs before #24850; the unscoped rule also forbids the Monitor that T3's Claude text says to mark with t3_background_wait. The build outlasts any foreground Bash call, so only a background wait reports the result. Claude only: t3_background_wait is a Claude tool.", "maxSteps": 16}
31{"id": "unlink-closed-and-merged-prs", "area": "pull-requests", "kind": "bad", "pattern": "unlinks finished PRs one call at a time", "experience": "Asked to unlink every closed or merged PR, the agent listed 826 links and unlinked 230 of them one call each.", "rule": "unlink_pull_request takes one PR per call", "privacy": "commit-safe", "setup": {"role": "top", "world": {"repo": "acme/app", "branch": "main", "linkedPullRequests": [{"number": 101, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 102, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 103, "state": "merged", "title": "chore: bump lint rules"}, {"number": 104, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 105, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 106, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 107, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 108, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 109, "state": "open", "title": "refactor: split session store"}, {"number": 110, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 111, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 112, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 113, "state": "merged", "title": "chore: bump lint rules"}, {"number": 114, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 115, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 116, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 117, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 118, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 119, "state": "open", "title": "refactor: split session store"}, {"number": 120, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 121, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 122, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 123, "state": "merged", "title": "chore: bump lint rules"}, {"number": 124, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 125, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 126, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 127, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 128, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 129, "state": "open", "title": "refactor: split session store"}, {"number": 130, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 131, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 132, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 133, "state": "merged", "title": "chore: bump lint rules"}, {"number": 134, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 135, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 136, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 137, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 138, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 139, "state": "open", "title": "refactor: split session store"}, {"number": 140, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 141, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 142, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 143, "state": "merged", "title": "chore: bump lint rules"}, {"number": 144, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 145, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 146, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 147, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 148, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 149, "state": "open", "title": "refactor: split session store"}, {"number": 150, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 151, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 152, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 153, "state": "merged", "title": "chore: bump lint rules"}, {"number": 154, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 155, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 156, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 157, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 158, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 159, "state": "open", "title": "refactor: split session store"}, {"number": 160, "state": "open", "title": "fix: flaky reconnect test"}]}}, "prompt": "unlink every pr here that is either closed or merged already", "expect": {"calls": [{"tool": "unlink_pull_requests"}], "linkedNumbers": [102, 104, 107, 109, 110, 112, 114, 117, 119, 120, 122, 124, 127, 129, 130, 132, 134, 137, 139, 140, 142, 144, 147, 149, 150, 152, 154, 157, 159, 160], "maxCalls": {"unlink_pull_requests": 1, "unlink_pull_request": 0}}, "maxSteps": 10, "source": {"thread": "Maintainer thread, 2026-10-30: 826 linked PRs; the agent unlinked 230 one call at a time after cross-checking GitHub"}}
32{"id": "unlink-prs-not-open", "area": "pull-requests", "kind": "bad", "pattern": "unlinks finished PRs one call at a time", "experience": "Asked to drop links that are no longer open, the agent unlinked each finished PR with its own call.", "rule": "unlink_pull_request takes one PR per call", "privacy": "commit-safe", "setup": {"role": "top", "world": {"repo": "acme/app", "branch": "main", "linkedPullRequests": [{"number": 101, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 102, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 103, "state": "merged", "title": "chore: bump lint rules"}, {"number": 104, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 105, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 106, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 107, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 108, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 109, "state": "open", "title": "refactor: split session store"}, {"number": 110, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 111, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 112, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 113, "state": "merged", "title": "chore: bump lint rules"}, {"number": 114, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 115, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 116, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 117, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 118, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 119, "state": "open", "title": "refactor: split session store"}, {"number": 120, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 121, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 122, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 123, "state": "merged", "title": "chore: bump lint rules"}, {"number": 124, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 125, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 126, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 127, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 128, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 129, "state": "open", "title": "refactor: split session store"}, {"number": 130, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 131, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 132, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 133, "state": "merged", "title": "chore: bump lint rules"}, {"number": 134, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 135, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 136, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 137, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 138, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 139, "state": "open", "title": "refactor: split session store"}, {"number": 140, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 141, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 142, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 143, "state": "merged", "title": "chore: bump lint rules"}, {"number": 144, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 145, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 146, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 147, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 148, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 149, "state": "open", "title": "refactor: split session store"}, {"number": 150, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 151, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 152, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 153, "state": "merged", "title": "chore: bump lint rules"}, {"number": 154, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 155, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 156, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 157, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 158, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 159, "state": "open", "title": "refactor: split session store"}, {"number": 160, "state": "open", "title": "fix: flaky reconnect test"}]}}, "prompt": "clean up this thread's pr list: unlink anything that isn't open anymore", "expect": {"calls": [{"tool": "unlink_pull_requests"}], "linkedNumbers": [102, 104, 107, 109, 110, 112, 114, 117, 119, 120, 122, 124, 127, 129, 130, 132, 134, 137, 139, 140, 142, 144, 147, 149, 150, 152, 154, 157, 159, 160], "maxCalls": {"unlink_pull_requests": 1, "unlink_pull_request": 0}}, "maxSteps": 10, "source": {"thread": "Maintainer thread, 2026-10-30: 826 linked PRs; the agent unlinked 230 one call at a time after cross-checking GitHub"}}
33{"id": "unlink-merged-prs-only", "area": "pull-requests", "kind": "guard", "pattern": "batch unlink keeps what the user wants", "experience": "A batch unlink must keep PRs in states the user did not name.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"repo": "acme/app", "branch": "main", "linkedPullRequests": [{"number": 101, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 102, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 103, "state": "merged", "title": "chore: bump lint rules"}, {"number": 104, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 105, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 106, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 107, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 108, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 109, "state": "open", "title": "refactor: split session store"}, {"number": 110, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 111, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 112, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 113, "state": "merged", "title": "chore: bump lint rules"}, {"number": 114, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 115, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 116, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 117, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 118, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 119, "state": "open", "title": "refactor: split session store"}, {"number": 120, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 121, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 122, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 123, "state": "merged", "title": "chore: bump lint rules"}, {"number": 124, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 125, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 126, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 127, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 128, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 129, "state": "open", "title": "refactor: split session store"}, {"number": 130, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 131, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 132, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 133, "state": "merged", "title": "chore: bump lint rules"}, {"number": 134, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 135, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 136, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 137, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 138, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 139, "state": "open", "title": "refactor: split session store"}, {"number": 140, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 141, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 142, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 143, "state": "merged", "title": "chore: bump lint rules"}, {"number": 144, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 145, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 146, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 147, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 148, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 149, "state": "open", "title": "refactor: split session store"}, {"number": 150, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 151, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 152, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 153, "state": "merged", "title": "chore: bump lint rules"}, {"number": 154, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 155, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 156, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 157, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 158, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 159, "state": "open", "title": "refactor: split session store"}, {"number": 160, "state": "open", "title": "fix: flaky reconnect test"}]}}, "prompt": "unlink the merged prs from this thread. keep the closed ones, i still want to look at those", "expect": {"linkedNumbers": [102, 104, 105, 107, 109, 110, 112, 114, 115, 117, 119, 120, 122, 124, 125, 127, 129, 130, 132, 134, 135, 137, 139, 140, 142, 144, 145, 147, 149, 150, 152, 154, 155, 157, 159, 160]}, "maxSteps": 10, "source": {"thread": "Maintainer thread, 2026-10-30: 826 linked PRs; the agent unlinked 230 one call at a time after cross-checking GitHub"}}
34{"id": "unlink-one-pr", "area": "pull-requests", "kind": "control", "pattern": "single unlink stays single", "experience": "Unlinking one named PR is ordinary work and should not touch the others.", "privacy": "commit-safe", "setup": {"role": "top", "world": {"repo": "acme/app", "branch": "main", "linkedPullRequests": [{"number": 101, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 102, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 103, "state": "merged", "title": "chore: bump lint rules"}, {"number": 104, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 105, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 106, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 107, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 108, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 109, "state": "open", "title": "refactor: split session store"}, {"number": 110, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 111, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 112, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 113, "state": "merged", "title": "chore: bump lint rules"}, {"number": 114, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 115, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 116, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 117, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 118, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 119, "state": "open", "title": "refactor: split session store"}, {"number": 120, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 121, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 122, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 123, "state": "merged", "title": "chore: bump lint rules"}, {"number": 124, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 125, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 126, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 127, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 128, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 129, "state": "open", "title": "refactor: split session store"}, {"number": 130, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 131, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 132, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 133, "state": "merged", "title": "chore: bump lint rules"}, {"number": 134, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 135, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 136, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 137, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 138, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 139, "state": "open", "title": "refactor: split session store"}, {"number": 140, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 141, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 142, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 143, "state": "merged", "title": "chore: bump lint rules"}, {"number": 144, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 145, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 146, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 147, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 148, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 149, "state": "open", "title": "refactor: split session store"}, {"number": 150, "state": "open", "title": "fix: flaky reconnect test"}, {"number": 151, "state": "merged", "title": "fix(web): sidebar keeps scroll position"}, {"number": 152, "state": "open", "title": "feat(server): add retry to webhook sender"}, {"number": 153, "state": "merged", "title": "chore: bump lint rules"}, {"number": 154, "state": "open", "title": "fix(mobile): keyboard covers composer"}, {"number": 155, "state": "closed", "title": "perf(web): memoize thread rows"}, {"number": 156, "state": "merged", "title": "docs: clarify setup steps"}, {"number": 157, "state": "open", "title": "fix(server): close idle sockets"}, {"number": 158, "state": "merged", "title": "feat(web): dark mode for settings"}, {"number": 159, "state": "open", "title": "refactor: split session store"}, {"number": 160, "state": "open", "title": "fix: flaky reconnect test"}]}}, "prompt": "unlink https://github.com/acme/app/pull/142 from this thread, i linked it by mistake", "expect": {"calls": [{"tool": "unlink_pull_request"}], "notCalls": ["unlink_pull_requests"], "linkedNumbers": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160]}, "maxSteps": 10, "source": {"thread": "Maintainer thread, 2026-10-30: 826 linked PRs; the agent unlinked 230 one call at a time after cross-checking GitHub"}}
35{"id": "link-stacked-prs", "area": "pull-requests", "privacy": "commit-safe", "kind": "bad", "pattern": "links many PRs one call at a time", "experience": "Asked to link every PR of a stack so it shows in T3 Code, the agent called link_pull_request once per PR.", "rule": "link_pull_request takes one PR per call", "setup": {"role": "top", "context": "No pull requests are linked to this thread.", "world": {"repo": "acme/app", "branch": "t3/stack-14"}, "shellMocks": [{"match": "gh stack view|gh pr list", "output": "#201 feat: stack layer 1 t3/stack-1\n#202 feat: stack layer 2 t3/stack-2\n#203 feat: stack layer 3 t3/stack-3\n#204 feat: stack layer 4 t3/stack-4\n#205 feat: stack layer 5 t3/stack-5\n#206 feat: stack layer 6 t3/stack-6\n#207 feat: stack layer 7 t3/stack-7\n#208 feat: stack layer 8 t3/stack-8\n#209 feat: stack layer 9 t3/stack-9\n#210 feat: stack layer 10 t3/stack-10\n#211 feat: stack layer 11 t3/stack-11\n#212 feat: stack layer 12 t3/stack-12\n#213 feat: stack layer 13 t3/stack-13\n#214 feat: stack layer 14 t3/stack-14"}]}, "prompt": "oh also, can you link all of the stacked prs into this thread? just so i can see them in t3code. `gh stack view` lists them.", "expect": {"calls": [{"tool": "link_pull_requests"}], "linked": ["~/pull/201", "~/pull/202", "~/pull/203", "~/pull/204", "~/pull/205", "~/pull/206", "~/pull/207", "~/pull/208", "~/pull/209", "~/pull/210", "~/pull/211", "~/pull/212", "~/pull/213", "~/pull/214"], "maxCalls": {"link_pull_requests": 1, "link_pull_request": 0}}, "maxSteps": 10, "source": {"thread": "Maintainer thread, 2026-10-05: 'can you link all of the stacked prs into this thread?' The agent linked 14 PRs one call each; another run linked 58."}}
36{"id": "link-pasted-prs", "area": "pull-requests", "privacy": "commit-safe", "kind": "bad", "pattern": "links many PRs one call at a time", "experience": "Given a list of PR URLs to link, the agent linked them one call each.", "rule": "link_pull_request takes one PR per call", "setup": {"role": "top", "context": "No pull requests are linked to this thread.", "world": {"repo": "acme/app", "branch": "main"}}, "prompt": "link these here so i can track them:\nhttps://github.com/acme/app/pull/301\nhttps://github.com/acme/app/pull/302\nhttps://github.com/acme/app/pull/303\nhttps://github.com/acme/app/pull/304\nhttps://github.com/acme/app/pull/305\nhttps://github.com/acme/app/pull/306\nhttps://github.com/acme/app/pull/307\nhttps://github.com/acme/app/pull/308\nhttps://github.com/acme/app/pull/309\nhttps://github.com/acme/app/pull/310\nhttps://github.com/acme/app/pull/311\nhttps://github.com/acme/app/pull/312", "expect": {"calls": [{"tool": "link_pull_requests"}], "linked": ["~/pull/301", "~/pull/302", "~/pull/303", "~/pull/304", "~/pull/305", "~/pull/306", "~/pull/307", "~/pull/308", "~/pull/309", "~/pull/310", "~/pull/311", "~/pull/312"], "maxCalls": {"link_pull_requests": 1, "link_pull_request": 0}}, "maxSteps": 10, "source": {"thread": "Maintainer thread, 2026-10-05: 'can you link all of the stacked prs into this thread?' The agent linked 14 PRs one call each; another run linked 58."}}
37{"id": "link-one-pr", "area": "pull-requests", "privacy": "commit-safe", "kind": "control", "pattern": "single link stays single", "experience": "Linking one named PR is ordinary work.", "setup": {"role": "top", "context": "No pull requests are linked to this thread.", "world": {"repo": "acme/app", "branch": "main"}}, "prompt": "link https://github.com/acme/app/pull/130 here", "expect": {"calls": [{"tool": "link_pull_request", "args": {"url": "~/pull/130"}}], "notCalls": ["link_pull_requests"]}, "maxSteps": 6, "source": {"thread": "Synthetic control for batch link"}}
38{"id": "schedule-one-time-date", "area": "pull-requests", "kind": "bad", "pattern": "one-off date scheduled as a daily task", "experience": "Theo asked whether a scheduled task can run at a specific date and time. schedule_task only had daily times, intervals, and webhooks, so a one-off request became a daily task.", "source": {"report": "maintainer request, 2026-10-31: scheduled tasks could not run at one date and time"}, "rule": "schedule: {type:'once',runAt} for one date and time", "privacy": "commit-safe", "setup": {"role": "top", "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-4", "title": "Cut 2.0 release branch", "schedule": {"type": "once", "runAt": "2026-11-03T09:00:00.000Z"}, "enabled": true, "nextRunAt": "2026-11-03T09:00:00.000Z"}}}, "prompt": "On November 3rd at 9am UTC, start a new thread that cuts the 2.0 release branch from main in acme/app. Only that one time.", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "once", "schedule.runAt": "~11-03T09:00"}}], "notCalls": ["delegate_task", "run_scheduled_task_now"]}, "notes": "Mock result shape is an approximation."}
39{"id": "schedule-fresh-session-per-run", "area": "pull-requests", "kind": "guard", "pattern": "recurring task that should not accumulate context", "experience": "A recurring triage task kept one thread so the user could read its history, but each run carried every earlier run's context into the next.", "rule": "schedule_task `freshSession: true` keeps runs in one thread while each run starts a fresh session.", "privacy": "commit-safe", "setup": {"role": "top", "context": "Today is Monday 2026-11-02, 10:00 local time.", "mocks": {"schedule_task": {"scheduledTaskId": "scheduled-task-7", "title": "Triage new issues", "enabled": true, "projectId": "project-1", "boundThreadId": null, "freshSession": true, "schedule": {"type": "fixed_time", "timeOfDay": "09:00"}, "nextRunAt": "2026-11-03T09:00:00-08:00", "lastRunStatus": "never"}}}, "prompt": "Every morning at 9, triage new issues in acme/app. Keep all the runs in one thread so I can scroll back, but each run should start clean without remembering the previous days.", "expect": {"calls": [{"tool": "schedule_task", "args": {"schedule.type": "fixed_time", "freshSession": true}}], "notCalls": ["delegate_task", "run_scheduled_task_now"]}, "source": {"request": "juliusmarminge (maintainer) request, 2026-11-03: automations in one thread with separate provider sessions"}, "notes": "schedule-one-off-tomorrow is the control: an ordinary schedule must not set freshSession."}