Skip to content

fix(acp): ID-only command approvals keep the tracked command and grant the terminal - #26647

Merged
juliusmarminge merged 5 commits into
mainfrom
fix/issue-26620
Oct 29, 2026
Merged

juliusmarminge merged 5 commits into
mainfrom
fix/issue-26620

Conversation

@juliusmarminge

Copy link
Copy Markdown
Member

Problem

An ACP agent can send session/request_permission with only a toolCallId after it has already announced that tool call in the turn. Devin from the ACP Registry does this. T3 read only the request payload, so in Supervised mode the approval prompt showed Session <session id> instead of the command. The request's kind was also unknown, so accepting it recorded no command grant. Devin's next terminal/create was then refused, and the command never ran ("Failed to create terminal").

Reproduced on main (9e09926) in the web dev server, with a scripted ACP v1 agent registered as devin (Executable override) in Supervised mode. The agent announces an execute tool_call, sends an ID-only permission request, then calls terminal/create once the request is approved.

Change

When a permission request leaves out the title, raw input or kind, the ACP adapter now copies them from the tool call that the active turn of the same session is already tracking (withTrackedPermissionToolCall). This happens once, before anything else reads the request, so the prompt, the policy disposition and the grant all see the same request. Fields the request does send are kept, including fields it explicitly clears with null. A tracked kind is copied only when it is execute: other and custom kinds map to command approvals, so copying them would let an approved non-execute tool open terminals. An untracked tool call is left unchanged and still gets no execute grant.

Scope and approval

Fixes #26620

Verification

  • AcpAdapterV2.test.ts: the new table test fails on main and passes here.
    • grants an ID-only execute approval its tracked command: on main the prompt is Session mock-session-1 and terminalCreated is false.
    • does not grant execution to an ID-only approval of another tool: a tracked other kind is still refused.
    • The existing does not turn an unknown permission approval into an execute grant passes unchanged.
  • runtimeModel.test.ts: keeps an explicit permission request kind over the tracked tool call's kind and keeps explicitly cleared permission request fields over the tracked tool call (a kind: null request stays unknown).
  • AcpAdapterV2, AcpRegistryAdapterV2, runtimeModel and clientPolicy tests pass (145 tests). vp run check:changed passes.
  • Real app, web dev server, fresh databases (T3CODE_DEV_SEED=0), Supervised mode, scripted agent as devin, 1280×800, light theme.
Before (9e09926) After (51c60eb)
Approval prompt Session id-only-session-… the full python3 -c … command
After Approve terminal/create refused with "requires approval for terminal/create"; no approval-probe.txt terminal exits 0; approval-probe.txt contains ACP_APPROVAL_OK

Captures, 1280×800, light theme:

Before (9e09926) After (51c60eb)
Approval prompt Before: the approval prompt shows Session id-only-session-… (main 9e09926d97) After: the approval prompt shows the python3 command (51c60ebe00)
After Approve Before: after Approve, terminal/create is refused After: after Approve, the terminal exits 0

The after run used 51c60eb; the later head only changes how explicit null fields are handled, which this ID-only flow doesn't send. No render code changed; only the prompt text and the grant change.

Protocol transcript, before (agent ↔ T3)
2026-10-28T20:36:37.157Z agent->T3 {"method":"session/update","params":{"sessionId":"id-only-session-1793219795759","update":{"sessionUpdate":"tool_call","toolCallId":"call-1","title":"python3 -c \"from pathlib import Path; Path('approval-probe.txt').write_text('ACP_APPROVAL_OK\\n')\"","kind":"execute","status":"pending","rawInput":{"command":"python3 -c \"from pathlib import Path; Path('approval-probe.txt').write_text('ACP_APPROVAL_OK\\n')\""}}}}
2026-10-28T20:36:37.157Z agent->T3 {"id":1000,"method":"session/request_permission","params":{"sessionId":"id-only-session-1793219795759","toolCall":{"toolCallId":"call-1"},"options":[{"optionId":"allow_once","name":"Allow once","kind":"allow_once"},{"optionId":"reject_once","name":"Reject","kind":"reject_once"}]}}
2026-10-28T20:36:44.884Z T3->agent {"jsonrpc":"2.0","id":1000,"result":{"outcome":{"outcome":"selected","optionId":"allow_once"}}}
2026-10-28T20:36:44.884Z agent->T3 {"id":1001,"method":"terminal/create","params":{"sessionId":"id-only-session-1793219795759","command":"python3","args":["-c","from pathlib import Path; Path('approval-probe.txt').write_text('ACP_APPROVAL_OK\\n')"],"cwd":"/tmp/issue-lead-26620-r1/project"}}
2026-10-28T20:36:44.887Z T3->agent {"jsonrpc":"2.0","id":1001,"error":{"_tag":"Cause","code":-32603,"message":"The active T3 runtime policy requires approval for terminal/create. Request permission with session/request_permission before retrying.","data":[{"_tag":"Fail","error":{"code":-32603,"message":"The active T3 runtime policy requires approval for terminal/create. Request permission with session/request_permission before retrying."}}]}}
2026-10-28T20:36:44.887Z agent->T3 {"method":"session/update","params":{"sessionId":"id-only-session-1793219795759","update":{"sessionUpdate":"tool_call_update","toolCallId":"call-1","status":"failed","content":[{"type":"content","content":{"type":"text","text":"Failed to create terminal. Please try again."}}]}}}
2026-10-28T20:36:44.887Z agent->T3 {"method":"session/update","params":{"sessionId":"id-only-session-1793219795759","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"terminal/create failed: The active T3 runtime policy requires approval for terminal/create. Request permission with session/request_permission before retrying."}}}}
Protocol transcript, after (agent ↔ T3)
2026-10-28T20:37:33.242Z agent->T3 {"method":"session/update","params":{"sessionId":"id-only-session-1793219852734","update":{"sessionUpdate":"tool_call","toolCallId":"call-1","title":"python3 -c \"from pathlib import Path; Path('approval-probe.txt').write_text('ACP_APPROVAL_OK\\n')\"","kind":"execute","status":"pending","rawInput":{"command":"python3 -c \"from pathlib import Path; Path('approval-probe.txt').write_text('ACP_APPROVAL_OK\\n')\""}}}}
2026-10-28T20:37:33.242Z agent->T3 {"id":1000,"method":"session/request_permission","params":{"sessionId":"id-only-session-1793219852734","toolCall":{"toolCallId":"call-1"},"options":[{"optionId":"allow_once","name":"Allow once","kind":"allow_once"},{"optionId":"reject_once","name":"Reject","kind":"reject_once"}]}}
2026-10-28T20:37:42.167Z T3->agent {"jsonrpc":"2.0","id":1000,"result":{"outcome":{"outcome":"selected","optionId":"allow_once"}}}
2026-10-28T20:37:42.167Z agent->T3 {"id":1001,"method":"terminal/create","params":{"sessionId":"id-only-session-1793219852734","command":"python3","args":["-c","from pathlib import Path; Path('approval-probe.txt').write_text('ACP_APPROVAL_OK\\n')"],"cwd":"/tmp/issue-lead-26620-r1/project"}}
2026-10-28T20:37:42.184Z T3->agent {"jsonrpc":"2.0","id":1001,"result":{"terminalId":"t3-term-1"}}
2026-10-28T20:37:42.184Z agent->T3 {"id":1002,"method":"terminal/wait_for_exit","params":{"sessionId":"id-only-session-1793219852734","terminalId":"t3-term-1"}}
2026-10-28T20:37:42.217Z T3->agent {"jsonrpc":"2.0","id":1002,"result":{"exitCode":0,"signal":null}}
2026-10-28T20:37:42.218Z agent->T3 {"id":1003,"method":"terminal/release","params":{"sessionId":"id-only-session-1793219852734","terminalId":"t3-term-1"}}
2026-10-28T20:37:42.220Z T3->agent {"jsonrpc":"2.0","id":1003,"result":{}}
2026-10-28T20:37:42.220Z agent->T3 {"method":"session/update","params":{"sessionId":"id-only-session-1793219852734","update":{"sessionUpdate":"tool_call_update","toolCallId":"call-1","status":"completed","rawOutput":{"exitCode":0}}}}
2026-10-28T20:37:42.220Z agent->T3 {"method":"session/update","params":{"sessionId":"id-only-session-1793219852734","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"terminal exited {\"exitCode\":0,\"signal\":null}"}}}}
Scratch ACP agent used for the runtime reproduction (not committed)
#!/usr/bin/env node
// Scratch ACP v1 agent for pingdotgg/t3code#26620: announces an execute tool_call, asks for
// permission by toolCallId only (as Devin does), then runs the command through terminal/create.
import { createInterface } from "node:readline";
import { appendFileSync } from "node:fs";

const LOG = process.env.ID_ONLY_AGENT_LOG;
const log = (dir, msg) => LOG && appendFileSync(LOG, `${new Date().toISOString()} ${dir} ${JSON.stringify(msg)}\n`);
const send = (msg) => {
  log(">>", msg);
  process.stdout.write(JSON.stringify({ jsonrpc: "2.0", ...msg }) + "\n");
};
let nextId = 1000;
const pending = new Map();
const request = (method, params) =>
  new Promise((resolve) => {
    const id = nextId++;
    pending.set(id, resolve);
    send({ id, method, params });
  });
const notify = (sessionId, update) => send({ method: "session/update", params: { sessionId, update } });

const COMMAND = "python3";
const ARGS = ["-c", "from pathlib import Path; Path('approval-probe.txt').write_text('ACP_APPROVAL_OK\\n')"];
const COMMAND_LINE = `${COMMAND} -c "from pathlib import Path; Path('approval-probe.txt').write_text('ACP_APPROVAL_OK\\n')"`;
let turn = 0;
const cwd = "/tmp/issue-lead-26620-r1/project";

async function prompt(id, params) {
  const sessionId = params.sessionId;
  const toolCallId = `call-${++turn}`;
  notify(sessionId, {
    sessionUpdate: "tool_call",
    toolCallId,
    title: COMMAND_LINE,
    kind: "execute",
    status: "pending",
    rawInput: { command: COMMAND_LINE },
  });
  const permission = await request("session/request_permission", {
    sessionId,
    toolCall: { toolCallId },
    options: [
      { optionId: "allow_once", name: "Allow once", kind: "allow_once" },
      { optionId: "reject_once", name: "Reject", kind: "reject_once" },
    ],
  });
  const selected = permission.result?.outcome?.outcome === "selected" ? permission.result.outcome.optionId : null;
  let text;
  if (selected !== "allow_once") {
    notify(sessionId, { sessionUpdate: "tool_call_update", toolCallId, status: "failed", rawOutput: { error: "rejected" } });
    text = `Permission outcome: ${JSON.stringify(permission.result ?? permission.error)}`;
  } else {
    const created = await request("terminal/create", { sessionId, command: COMMAND, args: ARGS, cwd });
    if (created.error) {
      notify(sessionId, {
        sessionUpdate: "tool_call_update",
        toolCallId,
        status: "failed",
        content: [{ type: "content", content: { type: "text", text: "Failed to create terminal. Please try again." } }],
      });
      text = `terminal/create failed: ${created.error.message}`;
    } else {
      const terminalId = created.result.terminalId;
      const exit = await request("terminal/wait_for_exit", { sessionId, terminalId });
      await request("terminal/release", { sessionId, terminalId });
      notify(sessionId, {
        sessionUpdate: "tool_call_update",
        toolCallId,
        status: "completed",
        rawOutput: { exitCode: exit.result?.exitCode ?? null },
      });
      text = `terminal exited ${JSON.stringify(exit.result ?? exit.error)}`;
    }
  }
  notify(sessionId, { sessionUpdate: "agent_message_chunk", content: { type: "text", text } });
  send({ id, result: { stopReason: "end_turn" } });
}

createInterface({ input: process.stdin }).on("line", (line) => {
  if (!line.trim()) return;
  const msg = JSON.parse(line);
  log("<<", msg);
  if (msg.method === undefined && pending.has(msg.id)) {
    const resolve = pending.get(msg.id);
    pending.delete(msg.id);
    resolve(msg);
    return;
  }
  switch (msg.method) {
    case "initialize":
      return send({
        id: msg.id,
        result: { protocolVersion: 1, agentCapabilities: { loadSession: false }, authMethods: [] },
      });
    case "session/new":
      return send({ id: msg.id, result: { sessionId: `id-only-session-${Date.now()}` } });
    case "session/prompt":
      return void prompt(msg.id, msg.params);
    case "session/cancel":
      return;
    default:
      if (msg.id !== undefined) send({ id: msg.id, error: { code: -32601, message: `Method not found: ${msg.method}` } });
  }
});

Not checked: a real Devin binary (needs a Devin account) and the desktop and mobile clients. The change is server-side, and they render the same approval data.

Implemented with Claude Opus 5.5 (Claude Code) and reviewed with GPT-6.1-Sol (Codex), orchestrated in T3 Code.

juliusmarminge and others added 5 commits October 28, 2026 20:04
…mand

An ACP agent in Supervised mode reports its execute tool call, then asks
session/request_permission with only the toolCallId. The approval prompt
falls back to "Session <id>" and approving it records no execute grant,
so the agent's terminal/create is refused.

These tests reproduce #26620.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ACP allows session/request_permission to name its tool call by ID only.
Devin does this after announcing the execute call, so the prompt showed
"Session <id>", the kind was unknown, and approving recorded no command
grant, which made the agent's terminal/create fail.

The permission handler now fills the omitted title, kind and rawInput
from the tool call the active turn tracks for that toolCallId in the
same session, before the prompt, disposition, flavor hooks and grant
read the request. Fields the request sends still win, and an untracked
toolCallId is left unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the tracked call

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 28, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 553a9cb

Macroscope's review found this PR approvable — ID-only ACP approvals now show the tracked command and enable terminal execution through the existing approval flow. Session matching, execute-only inheritance and unchanged approval scopes keep the correction narrowly bounded.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 9826dbb and 553a9cb.

Files selected for processing: 5
  • apps/server/scripts/acp-mock-agent.ts
  • apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts
  • packages/provider-acp/src/server/adapter.ts
  • packages/provider-acp/src/server/runtimeModel.test.ts
  • packages/provider-acp/src/server/runtimeModel.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

Walkthrough

The ACP adapter now enriches matching active-session permission requests with omitted fields from the tracked tool call. Explicit native values remain authoritative, and only an omitted kind on a tracked execute call is inferred as execute. Tests cover ID-only approvals and terminal outcomes for execute and other.

Changes: ACP permission context

Layer / File(s) Summary
Permission field enrichment
packages/provider-acp/src/server/runtimeModel.ts, packages/provider-acp/src/server/runtimeModel.test.ts
withTrackedPermissionToolCall fills undefined title and raw input from tracked state. It sets an undefined kind to execute only when the tracked call is an execute call. Tests check that explicit values, including null, remain unchanged.
Active-session permission wiring
packages/provider-acp/src/server/adapter.ts
The permission handler enriches requests when their session matches the active session and tracked tool-call state is available. Other requests use the native parameters unchanged.
Approval outcome test scenario
apps/server/scripts/acp-mock-agent.ts, apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts
The mock agent can emit a pending git status tool call and wait. Adapter tests check the prompt and terminal-creation outcome for ID-only approvals with execute and other kinds.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

  • pingdotgg/t3code#26620 — Reports the same ID-only ACP permission request losing tracked command context and blocking terminal execution.

Possibly related PRs

  • pingdotgg/t3code#26624 — Enriches ID-only permission requests from the active turn’s tracked tool call; this PR also preserves explicit nulls and limits execute-kind inference to tracked execute calls.
  • pingdotgg/t3code#20145 — Addresses command-approval presentation; this PR supplies tracked ACP title and raw input for ID-only approvals.

Suggested reviewers: t3dotgg, stienswout

Merge Risk: Minimal · up to 553a9

The ID-only approval path appears to preserve the intended terminal permission distinction. No merge-blocking issue was identified.

Pre-merge checks: Passed 4
Check name Status Explanation
Description check Passed The description clearly covers the problem, change, scope, approval reference, focused verification, observed results, limitations, screenshots, and agent attribution. It matches the required template…
Title check Passed The title clearly identifies the ACP ID-only approval fix and its primary outcome: preserving the tracked command and granting terminal execution. It is concise and specific.
Linked Issues check Passed Issue #26620 requires ID-only permission requests to use the tracked command, authorize a tracked execute request, and deny execution for unknown or non-execute tools. The adapter enriches matching re…
Out of Scope Changes check Passed The changes stay within issue #26620. The adapter and runtime-model changes implement request enrichment. The ACP mock-agent option and added tests provide coverage for the reported protocol cases. No…
Full details: Description check

Explanation

The description clearly covers the problem, change, scope, approval reference, focused verification, observed results, limitations, screenshots, and agent attribution. It matches the required template and explains the cross-component fix.

Full details: Linked Issues check

Explanation

Issue #26620 requires ID-only permission requests to use the tracked command, authorize a tracked execute request, and deny execution for unknown or non-execute tools. The adapter enriches matching requests before policy and grant processing. The helper preserves request fields, including explicit null values, and copies kind only for tracked execute calls. The added tests cover the execute, other-kind, unknown-kind, explicit-kind, and explicit-null cases.

Full details: Out of Scope Changes check

Explanation

The changes stay within issue #26620. The adapter and runtime-model changes implement request enrichment. The ACP mock-agent option and added tests provide coverage for the reported protocol cases. No unrelated production behavior is identified.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 21.6 KiB 21.6 KiB 0 B (0.0%) 24.4 KiB ✅
Codex Live turn messages 2 2 0 (0.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.3 KiB 1.3 KiB 0 B (0.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.9 KiB 21.9 KiB 0 B (0.0%) 24.4 KiB ✅
Claude Live turn messages 2 2 0 (0.0%) 8 ✅

Baseline: 9826dbb · PR result: 553a9cb · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@juliusmarminge juliusmarminge added bugfix Fix PR opened by an issue lead; enters the PR loop pr:needs-julius Needs Julius: route gate, unverifiable surface, or blocked labels Oct 28, 2026
@juliusmarminge
juliusmarminge merged commit b287102 into main Oct 29, 2026
40 checks passed
@juliusmarminge
juliusmarminge deleted the fix/issue-26620 branch October 29, 2026 00:17
aorwall added a commit to aorwall/t3code that referenced this pull request Oct 29, 2026
Merges upstream `pingdotgg/t3code` `fe219c4de1` (95 commits, base
`49a9e80e0b`) into the fork. Branched from `main`; no open merge PR to
stack on.

## Resolution

15 conflicts, each resolved by the verdict `preflight.mjs` printed for
it. The full record is in `docs/fork/upstream-merge-log.md`
(2026-10-29). The main points:

- **`LegacySidebar.tsx` deleted upstream** (pingdotgg#21039). The deletion is
taken. Its fork deltas (touch long-press, Add project gate) already live
in `Sidebar.tsx` / `SidebarThreadRow.tsx`. The file's paths are removed
from three inventory entries, and the helper only it used is dropped
from `Sidebar.logic.ts`.
- **Composer access mode.** Upstream's simpler composer (pingdotgg#25869) shows
the Access control in three places: the Permissions chip, the
compact-menu section, and the effort-menu section in
`ComposerControlsBar.tsx`. All three are gated on `FEATURES.accessMode`.
`ComposerControlsBar.tsx` is added to `chat-composer-gates`.
- **Sidebar.** Upstream's boring-sidebar project folders are combined
with the fork's browse/closed/subtask filter. Upstream moved the row's
meta line into a `metaLine` variable, and the fork's owner name and
parent/child task links now live inside it.
- **Settings.** The boring-sidebar Labs rows are client settings and are
not gated. Simlock hosts join the `serverBehaviorSettings` rows, and the
`simlock-hosts` search id is mapped to that flag. The new Fallback model
row is hidden together with the default model row
(`defaultModelSetting`).
- **`AGENTS.md`.** Upstream now has agents check user-visible changes in
a real client without asking first. The fork's version says the same,
still pointing at the fork's skills.
- `agentContextBudget.json` uses upstream's numbers, plus the fork's 108
bytes for `ProjectScript.port`. The value `46015` was measured by
running the budget test.
- `pnpm-lock.yaml` was re-derived from upstream's copy.

**Merge stats:** 595 files landed vs 593 in the upstream range (the gap
is explained in the tracker entry). Fork delta: 766 files, unchanged.

## Verification

`verify.mjs` was run in full. These checks pass: duplicate-adds,
tripwires, resolution-check, unsupported-methods, lockfile, fmt, lint,
typecheck, build. In the test step, `web` and `client-runtime` pass. The
same failures as the 2026-10-27 and 2026-10-28 merges remain, each
confirmed failing alone:
- `t3`: `RepositoryIdentityResolver`, `GitVcsDriver`,
`CodexChatGptAuth`, `CodexManagedRuntime` (the known `/tmp` sticky-bit
gap)
- `provider-opencode`: `OpenCodeServerLedger`

## Usable as-is
- Boring sidebar project folders, two-line folder rows, and hiding empty
projects (pingdotgg#21039, pingdotgg#26387, pingdotgg#26391, pingdotgg#26463)
- Simpler composer with grouped controls and effort words (pingdotgg#25869). The
access-mode parts stay gated.
- Pending approvals on their timeline row (pingdotgg#26255); tool call diffs open
in the diff panel (pingdotgg#26304); compaction row timer (3ef1610)
- Many web fixes: chat tables, inline code double-click, numbered
composer lists, image chips, scroll-to-end, tool groups after a
usage-limit pause, long-thread timeline perf (pingdotgg#26727)
- New Claude threads default to Opus 5.5 (pingdotgg#26358)
- PR files: mark viewed with shift-click, and the diff follows the file
tree (pingdotgg#26349, pingdotgg#26730). These are reachable only where the PR surface is
(`pullRequestSurface`).

## Unsupported in Moatless / needs implementation
- **GitHub sign-in through the T3 Code for GitHub app** (pingdotgg#26245):
`sourceControl.signInStart`, `signInWait`, `signInCancel`, `appAccess`.
New `UnsupportedMethodError` entries.
- **Simlock device pools** (pingdotgg#25259, pingdotgg#26706): `device.simlockIdentity`
(new union entry) and the `simlockDeviceHostsEnabled` server setting.
Part of the device hub gap.
- **Fallback model when the default provider is out of usage** (pingdotgg#26164):
the `fallbackModelSelection` server setting cannot be saved, because
`server.updateSettings` is refused. The row is hidden.
- Managing GitHub maintainer edits (pingdotgg#25588): in the pull-request write
family Moatless does not dispatch.
- T3 Connect tunnel usage and T3 Chat account push (pingdotgg#26369, pingdotgg#25931):
relay/cloud, decided out.
- Mobile dynamic workflows in the Agents sheet (pingdotgg#18642) and the mobile
app fixes: mobile against Moatless is unverified.

## Backend behavior to consider reproducing in Moatless
These are listed under _Runtime fixes upstream made to its own server_
in `docs/fork/gaps.md`:
- Falling back to another provider when the default is out of usage
(pingdotgg#26164), and refreshing usage when a turn hits its limit (pingdotgg#26234); no
replay of stale Codex usage limits (pingdotgg#26331)
- Settling a thread at once when its watched PR merges (pingdotgg#26395).
Moatless reads no PR for settlement today, so this is a decision rather
than a fix.
- Loading long thread history in bounded pages (pingdotgg#26240); publishing
local git status before the remote lookup (pingdotgg#21046); releasing completed
PR sweep data (pingdotgg#25177)
- Thread links that outside MCP clients can open (pingdotgg#26642); listing
snoozed threads across projects in one MCP call (pingdotgg#26442)
- Provider approval and subagent fixes: Claude command approvals
(pingdotgg#20145), ACP ID-only approvals (pingdotgg#26647), Cursor subagents (pingdotgg#26592),
OpenCode subagent runtime mode (pingdotgg#26302), Codex shadow-home locks
(c4606e5); shorter remote reconnect and command delays (pingdotgg#25754)

## After merging
- Disable the new upstream workflow: `gh workflow disable
mobile-fingerprint-label.yml --repo soaplabs/t3code`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---
Moatless task:
https://moatless.soaplabstest.com/tasks/b2df0ac3-40ce-4c78-a67d-b561b3d8772d
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Oct 29, 2026
## What's Changed
* docs(agents): agents verify in a browser or simulator without asking by @juliusmarminge in pingdotgg/t3code#26637
* feat(pull-requests): manage GitHub maintainer edits by @Bil0000 in pingdotgg/t3code#25588
* fix(web): image chips drop the pill and match other chips by @flamboh in pingdotgg/t3code#26379
* fix(mobile): align settled and active thread rows by @PixPMusic in pingdotgg/t3code#26622
* fix(server): publish local git status before remote lookup by @JustYannicc in pingdotgg/t3code#21046
* fix(server): outside MCP clients get thread links they can open by @t3dotgg in pingdotgg/t3code#26642
* fix(web): double-click selects the whole inline code span by @t3dotgg in pingdotgg/t3code#26639
* fix(acp): ID-only command approvals keep the tracked command and grant the terminal by @juliusmarminge in pingdotgg/t3code#26647
* fix(cursor): subagent ls, lint and MCP results no longer fail the parent run by @juliusmarminge in pingdotgg/t3code#26592
* perf(web): long-thread timeline rebuilds 45 → 9 ms, tool updates 12 → 0.3 ms by @juliusmarminge in pingdotgg/t3code#26727
* fix(mobile): a thread opened while reconnecting no longer turns into a new draft by @shivamhwp in pingdotgg/t3code#26565
* fix(web): keep settled drop hint inside the sidebar viewport by @Yash-Singh1 in pingdotgg/t3code#26736
* feat(devices): Simlock setup uses each machine's agent-device token, checks tokens, and lists the pool through worker reconnects by @juliusmarminge in pingdotgg/t3code#26706
* fix(web): pull request diff follows the file tree, with a hide-tests filter by @juliusmarminge in pingdotgg/t3code#26730
* fix(mobile): delivered messages no longer reappear at the bottom of long threads by @juliusmarminge in pingdotgg/t3code#26739
* ci(mobile): fork PRs now get the Native Change label by @juliusmarminge in pingdotgg/t3code#26755
* fix(claude): command approvals show the command, not Claude's description of it by @maria-rcks in pingdotgg/t3code#20145
* docs: add nixpkgs installation instructions by @0x4bs3nt in pingdotgg/t3code#26750
* feat(server,web,mobile): fall back when the default provider is out of usage by @maria-rcks in pingdotgg/t3code#26164
* fix(ci): fork PRs now resolve in workflow_run PR lookups by @juliusmarminge in pingdotgg/t3code#26779


**Full Changelog**: pingdotgg/t3code@v0.1.4-nightly.20261028.3841...v0.1.4-nightly.20261029.3853

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.1.4-nightly.20261029.3853
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fix PR opened by an issue lead; enters the PR loop pr:needs-julius Needs Julius: route gate, unverifiable surface, or blocked size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ACP ID-only command approvals lose context and leave terminal execution blocked

1 participant