Skip to content

fix(server): a turn that adds a Git remote shows "Add to another machine" right away - #29947

Open
juliusmarminge wants to merge 3 commits into
mainfrom
fix/issue-29929
Open

juliusmarminge wants to merge 3 commits into
mainfrom
fix/issue-29929

Conversation

@juliusmarminge

Copy link
Copy Markdown
Member

Problem

When an agent's turn gives a project its first Git remote (git remote add, gh repo create --push), the composer's "Run on" menu and its "Add to another machine" item stay hidden for up to a minute. The server keeps serving the project's cached repositoryIdentity: null until two 1-minute caches expire: the ProjectEnrichmentService identity cache and the RepositoryIdentityResolver negative cache.

Reproduced on main at 61c3794 with a dev server on a fresh database. A Claude (Sonnet 5.5) thread ran git remote add origin https://github.com/t3-triage/agent-29929.git and finished at 04:29:06Z. GET /api/orchestration/shell still returned repositoryIdentity: null for that project and showed the GitHub identity only 55 s after the turn ended. The same happens with a plain shell git remote add: 53 s.

Change

  • When a turn finishes, RunFinalizationObserver.refresh starts a background refresh of the thread's project identity, keyed by project. A newer turn's refresh replaces a running one, the same way the pull-request lookup there already runs. The next turn never waits for it, and forge discovery for self-hosted remotes can take seconds.
  • The refresh reads the identity through the normal caches first (readRepositoryIdentity). A project that already has an identity stops there and spawns no forced git lookup.
  • Only when the identity is missing does it call the new ProjectEnrichmentService.refreshRepositoryIdentity. That re-resolves past the resolver's negative cache and, when it finds an identity, replaces the cached enrichment entry and publishes the change to shell subscribers.
  • A folder that still has no remote publishes nothing, so remote-less projects don't push a shell frame after every turn. The favicon cache is left alone.

Clients need no change: web, desktop and mobile already apply enrichment changes from the shell stream.

Scope and approval

Fixes #29929

Triaged via the automated issue flow.

Verification

  • vp test run apps/server/src/orchestration-v2/RunFinalizationService.test.ts: 10/10 pass. Three new tests:

    • resolves the identity of a project whose remote was added during the turn: real temp git repo, cached null, remote added, turn finalized, then the identity is published. On main it never publishes. In its first form, which awaited the refresh inline, it failed with the project's repository identity is still null after the turn added a remote. Since the refresh moved to the background, all three new tests fail on main by hitting the test timeout rather than an assertion, because the event they await never arrives.
    • keeps a known identity's resolver caches when a turn outlasts the enrichment TTL: a project with a known identity, a turn after 61 s, and the resolver never receives refresh: true.
    • returns from the post-turn refresh while the identity resolver is still running: finalization returns while a gated resolver is pending, and the identity is published once it is released.
  • RunFinalizationQueue, DelegatedCompletionDelivery, runtimeLayer and ProjectEnrichmentService tests pass. RepositoryIdentityResolver tests pass 11/11 with GIT_CONFIG_GLOBAL=/dev/null; the test VM's global url.insteadOf rewrite breaks 2 of them on main too.

  • vp run check:changed passes: format, lint, knip, typecheck (3 packages), tests (6 files).

  • Runtime transcript, same driver and agent prompt on fresh dev databases. The driver polls the shell snapshot every 2 s after the turn ends:

    main 61c3794 this branch 2a5b348
    turn ends with origin set 04:29:06Z 05:01:48Z
    identity in shell snapshot +55 s +2 s (first poll after the turn)
    # main
    TURN DONE 04:29:06Z
    +0s after turn identity=None
    ...
    +53s after turn identity=None
    +55s after turn identity=https://github.com/t3-triage/agent-29929.git
    # this branch
    TURN DONE 05:01:48Z
    +0s after turn identity=None
    +2s after turn identity=https://github.com/t3-triage/agent-29929.git
    

Not checked:

  • Before/after captures of the "Run on" menu with a second linked machine; I had no file host to upload them to. No render code changes, and the menu reads the repositoryIdentity shown above.
  • A remote added outside a turn (integrated terminal, plain shell) still waits for the cache to expire, as on main.

Model and harness: Claude Opus 5.5 (lead, red test, implementation, review B) and GPT-6.1 Sol (review A), run through T3 Code with the Claude Code and Codex harnesses.

🤖 Generated with Claude Code

juliusmarminge and others added 3 commits November 3, 2026 04:35
A project whose enrichment cached "no repository" keeps a null
repositoryIdentity after a turn adds a Git remote, until the one-minute
caches expire. This test reproduces #29929: it fails on main because the
post-turn finalization refresh never re-resolves the project's identity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a turn ran `git remote add` or `gh repo create --push` in a project
with no repository identity, both the enrichment cache and the resolver's
negative cache kept reporting "no repository" for up to a minute, hiding
"Add to another machine" and the "Run on" menu.

Post-turn finalization now re-resolves the identity past both caches for
the thread's project when it has none yet, alongside the existing
workspace and VCS refresh. Projects that already have an identity pay
only an in-memory peek. A failure is logged and does not affect the turn.

Fixes #29929

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…for missing identities

The gate now reads through the enrichment cache instead of peeking it, so a
known identity past its 1-minute TTL no longer forces a resolver refresh.
The refresh runs in a per-project FiberMap so forge probes cannot delay the
next queued turn, and a forced resolve that still finds no remote publishes
nothing. The thread shell is read after the workspace and VCS refreshes
again, so its failure cannot interrupt them.

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 Nov 3, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 2a5b348

Macroscope's review found this PR approvable — Projects that gain their first Git remote during a turn now publish their repository identity promptly, making existing machine options available without waiting for cache expiry. The refresh runs in the background and avoids forced lookups for known identities or notifications for unchanged misses.

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

@github-actions

github-actions Bot commented Nov 3, 2026

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.2 KiB 1.2 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: 61c3794 · PR result: 2a5b348 · 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 the bugfix Fix PR opened by an issue lead; enters the PR loop label Nov 3, 2026
@coderabbitai

coderabbitai Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Walkthrough

Run finalization now starts an asynchronous repository identity refresh for an existing project when its cached identity is missing. The enrichment service forces a resolver refresh and publishes a resolved identity.

Changes: Repository identity refresh

Layer / File(s) Summary
Add repository identity refresh
apps/server/src/project/ProjectEnrichmentService.ts
The service exposes refreshRepositoryIdentity. It forces a resolver refresh, returns without publishing if the resolver returns null, and otherwise invalidates the repository identity cache before running the existing resolution flow.
Refresh identity after a turn
apps/server/src/orchestration-v2/RunFinalizationService.ts, apps/server/src/orchestration-v2/RunFinalizationService.test.ts, apps/server/src/orchestration-v2/RunFinalizationQueue.test.ts, apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts, apps/server/src/orchestration-v2/runtimeLayer.test.ts
The observer looks up the thread before checking its branch, then starts a per-project asynchronous refresh when the project exists and has no repository identity. Tests cover a remote added during a turn, refresh after the enrichment TTL, and completion while identity resolution is still running. Test mocks provide the added service methods and dependencies.

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

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: t3dotgg, maria-rcks

Merge Risk: Minimal · up to 2a5b3

The post-turn identity refresh has no identified merge-blocking issue. Normal checks remain appropriate before merging.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title names the main change: a turn that adds a Git remote now refreshes repository identity right away, so the "Add to another machine" item appears without waiting for cache expiry. It uses the …
Description check Passed
Linked Issues check Passed The PR meets the coding requirements for [#29929]. RunFinalizationObserver.refresh starts a per-project background identity refresh after a turn. It first reads the normal enrichment caches. It forc…
Out of Scope Changes check Passed The changes stay within [#29929]. Production changes implement post-turn repository identity refresh and shell publication. Test changes provide the new service method in mocks and verify the required…
Full details: Title check

Explanation

The title names the main change: a turn that adds a Git remote now refreshes repository identity right away, so the "Add to another machine" item appears without waiting for cache expiry. It uses the conventional commit form and matches the objectives and the file changes.

Full details: Linked Issues check

Explanation

The PR meets the coding requirements for [#29929]. RunFinalizationObserver.refresh starts a per-project background identity refresh after a turn. It first reads the normal enrichment caches. It forces resolver refresh only when the identity is missing. ProjectEnrichmentService.refreshRepositoryIdentity invalidates the enrichment identity cache and publishes a found identity to shell subscribers. It publishes nothing when no remote exists. RunFinalizationService.test.ts covers remote discovery, known-identity cache preservation, and non-blocking finalization.

Full details: Out of Scope Changes check

Explanation

The changes stay within [#29929]. Production changes implement post-turn repository identity refresh and shell publication. Test changes provide the new service method in mocks and verify the required behavior. No unrelated UI, client, favicon, or outside-turn remote refresh changes were added.

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

Warning

Some tools did not complete. Review the errors below.

ast-grep (0.51.1)

ast-grep could not parse rule config: /ast-grep-rules/ast-grep-essentials/rules/csharp/security/weak-hash-sha1-csharp.yml

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 61c3794 and 2a5b348.

Files selected for processing: 6
  • apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts
  • apps/server/src/orchestration-v2/RunFinalizationQueue.test.ts
  • apps/server/src/orchestration-v2/RunFinalizationService.test.ts
  • apps/server/src/orchestration-v2/RunFinalizationService.ts
  • apps/server/src/orchestration-v2/runtimeLayer.test.ts
  • apps/server/src/project/ProjectEnrichmentService.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.

@juliusmarminge juliusmarminge added the pr:gate-failed A land gate failed label Nov 3, 2026

This branch has not been deployed

No deployments
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:gate-failed A land gate failed 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.

"Add to another machine" stays hidden for up to a minute after a project gets a Git remote

1 participant