Skip to content

fix(t3code): read environment capabilities; stop polling the Keychain while unpaired - #98

Open
DevVig wants to merge 1 commit into
mainfrom
fix/t3code-capabilities-keychain
Open

fix(t3code): read environment capabilities; stop polling the Keychain while unpaired#98
DevVig wants to merge 1 commit into
mainfrom
fix/t3code-capabilities-keychain

Conversation

@DevVig

@DevVig DevVig commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Two independent fixes found while checking the paired contract against a live
T3 Code (Nightly) 0.0.29-nightly.20260725.899
environment.

1. Environment capabilities were discarded

.well-known/t3/environment now returns a capabilities object that
EnvironmentDescriptor did not deserialize at all:

{ "repositoryIdentity": true, "connectionProbe": true,
  "threadSettlement": true, "threadSnooze": true,
  "serverSelfUpdate": "desktop-managed" }

So the adapter had no way to tell what this environment supports — and a
nightly can gain or lose a surface without the base version moving. Now read,
reset on unpair/disable, and surfaced in the diagnostic so the Settings card
names advertised-but-unbound surfaces rather than implying T3 offers nothing more.

capabilities() is deliberately unchanged — it still advertises only what
dispatch_action implements. Reading a flag is not implementing a lever.

2. The Keychain was polled every 900 ms while unpaired

load_credential() is a macOS Keychain read, and it ran on the adapter's normal
tick whenever no credential was stored — thousands of reads an hour for a state
only the user can change. Now backs off 3 s.

Contract findings, for the record

No version bump needed: 0.0.29-nightly.… is already inside the pinned range.

  • No navigation command exists. thread.jump.1…9 are
    THREAD_JUMP_KEYBINDING_COMMANDS — local UI keybindings, not dispatchable.
    Binding them would be the keystroke synthesis this adapter refuses.
  • No reasoning-effort concept exists. reasoningEffort appears nowhere;
    ModelSelection.options is still an untyped bag — literally the "provider
    option descriptors" the existing comment waits on. The near-misses are
    different things: RuntimeMode is an autonomy ladder
    (approval-requiredfull-access) and ProviderInteractionMode is
    default/plan.
  • new_session stays unadvertised. thread.turn.start needs an existing
    threadId, or bootstrap.createThread with projectId + title + a
    modelSelection.model we have no registry for.
  • Dispatchable but unbound: thread.settle/unsettle,
    thread.snooze/unsnooze, thread.archive/unarchive.

How this was tested

  • cargo test --workspace — 84 tests, 2 new: one decodes the verbatim live
    descriptor and asserts unknown keys (serverSelfUpdate, a string not a bool)
    don't break decoding; one covers the diagnostic.
  • cargo clippy --workspace --all-targets -- -D warnings and cargo fmt --all --check clean.
  • Verified live against the environment descriptor on port 3774 (note: not the
    3773 in the unit-test fixture).

Not verified

The stored credential is revoked, so apply_snapshot — and therefore the new
diagnostic on a real snapshot — could not be reached end to end. Needs a re-pair.

Docs for these findings land with the controller-lock PR, which owns
docs/adapters.md; kept out of here to avoid a merge conflict between the two.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings July 25, 2026 13:19
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f8d3c02-8a7e-42ca-bf53-d259e54e27e6

📥 Commits

Reviewing files that changed from the base of the PR and between fcd0aba and a7fe05d.

📒 Files selected for processing (1)
  • crates/microbridged/src/t3code.rs

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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the T3 Code adapter (crates/microbridged) to better reflect what a paired T3 environment actually supports (via .well-known/t3/environment) and to reduce unnecessary secure-store reads while the adapter is unpaired.

Changes:

  • Deserialize and track capabilities from the environment descriptor; reset them on disable/unpair and surface advertised-but-unwired surfaces in the adapter diagnostic.
  • Back off Keychain re-reads while unpaired (from every 900ms tick to every 3s).
  • Add unit tests covering decoding of a live descriptor shape (including unknown key types) and the new diagnostic behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 337 to 348
if credential.is_none() {
credential = load_credential().ok();
if credential.is_none() {
// Unpaired. `load_credential` reads the macOS
// Keychain, so retrying on the 900 ms tick means
// thousands of Keychain hits an hour for a state
// only the user can change. Back off; pairing is
// picked up on the next attempt.
retry_after = tokio::time::Instant::now() + UNPAIRED_RECHECK;
continue;
}
}
… while unpaired

Two independent fixes found while checking the paired contract against a live
T3 Code (Nightly) 0.0.29-nightly.20260725.899 environment.

Environment capabilities were being discarded. The descriptor at
`.well-known/t3/environment` now returns a `capabilities` object
(`threadSettlement`, `threadSnooze`, `connectionProbe`, `repositoryIdentity`,
`serverSelfUpdate`) that `EnvironmentDescriptor` did not deserialize at all, so
the adapter could not tell what this environment supports — a nightly can gain
or lose a surface without the base version moving. It is now read, reset on
unpair/disable, and reported in the adapter diagnostic, which names
advertised-but-unbound surfaces instead of implying T3 offers nothing more.

`capabilities()` is deliberately unchanged: it still advertises only what
`dispatch_action` implements. Reading a flag is not implementing a lever.

The Keychain was polled on every tick while unpaired. `load_credential()` is a
macOS Keychain read, and it ran on the 900 ms interval whenever no credential
was stored — thousands of reads an hour for a state only the user can change.
Now backs off 3 s, short enough that pairing still feels immediate.

Contract findings recorded for the follow-up (docs land with the controller-lock
PR, which owns docs/adapters.md):

- No navigation/selection command exists. `thread.jump.1…9` are
  THREAD_JUMP_KEYBINDING_COMMANDS, local UI keybindings, not dispatchable.
- No reasoning-effort concept exists: `reasoningEffort` appears nowhere and
  `ModelSelection.options` is still an untyped bag — the "provider option
  descriptors" the existing comment waits on. `RuntimeMode` is an autonomy
  ladder and `ProviderInteractionMode` is default/plan; neither is effort.
- `new_session` needs `bootstrap.createThread` with projectId + title + a
  model id we have no registry for, so it stays unadvertised.

Tested: `cargo test --workspace` (84 tests, 2 new — one decodes the verbatim
live descriptor and asserts unknown keys like `serverSelfUpdate` do not break
decoding; one covers the diagnostic). Verified against the live environment on
port 3774. Not exercised against a paired environment end to end: the stored
credential is revoked, so `apply_snapshot` could not be reached.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@DevVig
DevVig force-pushed the fix/t3code-capabilities-keychain branch from 953f09a to a7fe05d Compare July 25, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants