Skip to content

chore(sync): absorb upstream main through fe219c4de1 - #239

Merged
NoahHendrickson merged 1083 commits into
customfrom
claude/sync-2026-10-28
Oct 31, 2026
Merged

NoahHendrickson merged 1083 commits into
customfrom
claude/sync-2026-10-28

Conversation

@NoahHendrickson

@NoahHendrickson NoahHendrickson commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Problem

custom is 1069 upstream commits behind pingdotgg/t3code main (through fe219c4).

Change

Merges upstream main directly. 93 files conflicted. Upstream split ChatView, ChatComposer and MessagesTimeline into hooks and row modules, so those files were rebuilt from upstream's versions and the fork's fenced intents re-applied at their new homes (useComposerSend, useThreadLifecycle, ThreadActivityBanners, useComposerSelection, chatRouteCommands, Timeline*Row, AppearanceSettingsPanel).

Fork-side follow-ups in the merge:

  • GitHub PR automation provides its own GitHubRest layer, which upstream now builds only inside its GitHub driver.
  • Window fill colours follow upstream's new #000000 dark background.
  • New upstream lint rules are deferred for fork-owned code with baselines; over-long fork comments in upstream files were shortened.
  • Upstream-replaced files are fenced into knip ignores; dead fork CSS for removed upstream elements is deleted.

Two commits on top:

  • chore(lint): three fork comments in the vite configs failed upstream's new error-level no-low-value-comments rule and would have turned vp check red.
  • fix(web): upstream now keeps the thread mounted under the Settings and Usage modals and gates its global shortcuts on BackgroundThreadContext. The fork's own global-input hooks did not, so pressing 1-9 could answer the hidden thread's pending question, and the right Command dictation hotkey recorded into the hidden draft instead of the fallback toast (a session still preparing went on to record unseen). Both now stand down. The same commit restores the pre-sync ChatView focus effect, so clicking into a split pane no longer pulls the caret to the end of its prompt.
  • fix(ci): upstream's new agent context budget test pins the MCP tool list at upstream's size; the fork's GitHub PR automation adds 342 bytes to schedule_task. The budget now records the fork's size, and the budget gate compares a PR with its base (custom) instead of main, the upstream mirror, which would otherwise fail every fork PR. Folded into ci-on-custom with a guard row.
  • fix(web), permissions chip: upstream moved the composer permissions chip from a Select to a Menu, but the fork's ComposerRuntimeModeTrigger still rendered a Base UI Select.Trigger, which throws without a Select root. Any thread in a non-default permission mode crashed the composer. It now renders upstream's ComposerControl. Upstream's ScheduledTasksSettings case for a user-opened New task dialog cannot happen here (New task opens the fork's /scheduled page), so it is fenced and replaced with a case pinning that.
  • Review follow-ups: 1f1b565 re-homes fork behavior that upstream's split modules dropped (side chat services and toasts, New worktree from a previous worktree, multi-model design pills, per-pane Find, the compaction "Never" setting, Open automation routing, startup recovery URL, Glass Settings nav in the new modal). b9241ac keeps PR-review automations on their PR routing when a stored dedupe exists and makes the automation share the GitHub driver's sign-in. See the triage comment for each finding.
  • Custom merged in (437f531, c740366): brings fix(web): blank drafts no longer pile up in the sidebar #240 and fix(web): bring design mode back in line with the fork's design system #241. Upstream moved the right panel width hook from ChatView into ChatRightPanel, so design mode's minimum width now reaches it through a fenced minWidth prop (manifest watch entry and guard updated). Code from fix(web): blank drafts no longer pile up in the sidebar #240/fix(web): bring design mode back in line with the fork's design system #241 that tripped the sync's new lint rules is fixed: two over-long comments, a panel-room effect reason, and a browser test that now cleans up with using.

Scope and approval

Routine fork upstream sync, per .fork/AGENTS.md. Upstream code is taken as-is; the follow-up commits fix only fork code the sync broke.

Verification

  • vp run typecheck: all 36 packages pass.
  • Fork guards (vp test run src/__fork_guards__ from apps/web): 84 files, 627 tests pass. Three new cases (background digits, background dictation fallback, dictation preparing when backgrounded) each fail with their fix reverted.
  • vp lint: 0 errors. node .fork/lint-owned.mjs, vp run knip:check, vp fmt --check, and both GraphQL generate --checks pass.
  • Not run locally: full web/server/desktop/mobile suites (left to CI) and lint:mobile (ktlint/detekt not installed here). Known host-only failures on Apple Silicon, not regressions: the Windows cross-arch case in scripts/build-desktop-artifact.test.ts and DesktopLinuxUrlHandler theme-icon tests on APFS.
  • 28 upstream commits landed after fe219c4; they go in the next sync.

Reviewed with sol-loop: 3 rounds with gpt-6.1-sol on high. Sol verdict: approved

Follow-up fixes after review (8f55cb7, fa6bf57, 16b448d) reviewed with sol-loop: 2 rounds with gpt-6.1-sol on high. Sol verdict: approved

Claude Opus 5.5 in Claude Code (T3 Code).

🤖 Generated with Claude Code

t3dotgg and others added 30 commits October 27, 2026 01:22
… a giant heading (pingdotgg#25804)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…tgg#25695)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…gg#25829)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…5830)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…iB page kernels (pingdotgg#25825)

TEMPORARY workaround for an upstream Node bug: nodejs/node#67048
Node 26.9+ --build-sea writes linux-arm64 executables that 16/64 KiB page kernels cannot load.
linux-arm64 now injects with postject, and the archive build rejects incompatible ELF layouts.
One-time exception. Revert once Node ships a fix: grep for the Node issue URL and drop postject
from scripts/package.json.

Fixes pingdotgg#23433. Replaces pingdotgg#23443.

Co-authored-by: Lakshmi Tanmay <lakshmi@voltcrash.com>
Co-authored-by: maria-rcks <maria@kuuro.net>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s 4x faster (pingdotgg#25824)

Co-authored-by: Omar <76748259+omar9848492@users.noreply.github.com>
Co-authored-by: Carlos D. Escobar-Valbuena <carlosdavidescobar@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 (Claude Code) <noreply@anthropic.com>
Co-authored-by: maria-rcks <maria@kuuro.net>
Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com>
…h no list open (pingdotgg#25809)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…saves a draft twice (pingdotgg#25768)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ering (pingdotgg#25846)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…rning (pingdotgg#25819)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…first commit (pingdotgg#25817)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…rom screen effects (pingdotgg#25820)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…otgg#25837)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…otgg#25860)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…d one (pingdotgg#25839)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…s it unavailable (pingdotgg#25632)

Co-authored-by: Theo Browne <me@theo.li>
…otgg#25841)

Co-authored-by: jason chien <3936511+jason5545@users.noreply.github.com>
…b checks

agent-device 0.21.32 folds the iPhone Duo in Simlock's device set. The
folded Duo keeps its side rail inside a thread, so the hide-on-push tab
checks only apply to bottom tab bars. The Duo poses stay pending: alert
accept cannot see the open-link prompt on the Duo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…derive instead of syncing (pingdotgg#25813)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…effects (pingdotgg#25895)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
shivamhwp and others added 4 commits October 29, 2026 02:59
…otgg#24937)

Co-authored-by: PR Batch Tester <agent@local.test>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Merges 1069 upstream commits. Upstream split ChatView, ChatComposer and
MessagesTimeline into hooks and row modules, so those files were rebuilt
from upstream's versions with the fork's fenced intents re-applied at their
new homes (useComposerSend, useThreadLifecycle, ThreadActivityBanners,
useComposerSelection, chatRouteCommands, Timeline*Row, AppearanceSettingsPanel).

Notable fork-side follow-ups:
- GitHub PR automation provides its own GitHubRest, which upstream now
  builds only inside its GitHub driver.
- Window fill colours follow upstream's new #000000 dark background.
- New upstream lint rules are deferred for fork-owned code with baselines;
  over-long fork comments in upstream files were shortened.
- Upstream-replaced files are fenced into knip ignores; dead fork CSS for
  removed upstream elements is deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upstream's new error-level comment rule fails three fork comments in the
vite configs, which would turn CI's `vp check` red.

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

Upstream now keeps the thread mounted under the Settings and Usage modals
and gates its global shortcuts on BackgroundThreadContext. Fork hooks that
own global input did not:

- pending-question digits 1-9 could answer the hidden thread's question
- the dictation hotkey recorded into the hidden thread's draft instead of
  the fallback toast; a session still preparing went on to record unseen
  (stop() is a no-op before recording, so it is now cancelled)

Also restores the pre-sync ChatView focus effect that reads splitPane.focused
without depending on it, so clicking into a split pane keeps its caret.

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:XXL labels Oct 29, 2026

@NoahHendrickson NoahHendrickson left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Code-quality review of 51b3c52.

Scope: this is an upstream sync, so upstream code taken as-is was not re-litigated. I reviewed the fork-side work: the two commits on top, the fork intents re-applied in upstream's new split modules, the server layer resolutions, and the lint baselines. GitHub could not place inline comments on a diff this size, so file and line references are given below.

No structural regressions in the merge itself. No fork delta pushes a file past 1k lines (I checked every modified file against upstream fe219c4). The server resolutions (GitHubRest provided inside GitHubPrAutomation.layer, the isNonRepositoryGitStderr fence, the branch-follow layer wiring) are direct and give their reasons. The PR also merges cleanly with #238, which landed on custom after this branch was cut; the data-fork-* hooks #238 targets survive the merge.

1. The rule "this composer owns global input" has no single home

Each site rebuilds the rule from splitPane.focused and BackgroundThreadContext, in a different shape every time:

  • components/ChatView.tsx ~1684 and ~2827 build !backgroundThread && splitPane.focused (open-in-editor shortcut, useChatShortcuts).
  • components/chat/useComposerStash.ts ~691-709 uses two separate early returns.
  • custom/useComposerPendingUserInputCard.ts:123-127 uses two locals, two guards and two deps.
  • custom/voice/useForkDictationController.ts:126 folds both into a variable still called paneFocused.

This commit exists because two of those sites were missed when upstream added backgrounding. The next fork hook with a document listener will be missed the same way.

Move the rule to where the concept lives, in custom/splitThreads/splitPaneContext.ts:

/** This pane takes window-level input: focused in a split, and not behind Settings or Usage. */
export function useOwnsGlobalInput(): boolean {
  return useSplitPane().focused && !use(BackgroundThreadContext);
}

The two fork hooks each drop to one local, one guard and one dep. The fenced sites in upstream files can keep upstream's own !backgroundThread and swap only the fenced splitPane.focused for the hook. The overlap is harmless, and those fences stay one token wide.

Related, in useForkDictationController.ts: paneFocused now means "focused and not backgrounded", and the stop/cancel effect below it inherits that meaning silently. Its comment still says "a recording left running in a pane that loses focus". But opening Settings or Usage mid-recording now also stops the recording and transcribes it into the hidden thread's draft. That's a reasonable behavior, since before the sync the thread unmounted and the recording died with it. But it is new, and neither the comment nor the PR body says so. Rename the variable to ownsInput and have the comment cover both cases. Likewise, the comment above the digit listener in useComposerPendingUserInputCard.ts mentions only the split.

2. Non-blocking: lint baseline

The t3code/no-low-value-comments baseline in .fork/lint-deferred.mjs went from 6 to 876. The ratchet mechanism is fine, but at 876 this rule no longer gates fork code. Its comment still says the deferral covers "section dividers in the vendored design-mode engine". Consider giving the vendored engine its own scope, or updating that comment and filing a burn-down item, so the number keeps meaning something.

Verdict

Not approving: finding 1 is a cheap fix that removes a whole class of repeat bugs, and it should land with the commit that just paid for it. Everything else in the fork-side resolution looks sound.

@NoahHendrickson NoahHendrickson left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Reviewed commit 51b3c5201d7bcc668efadd4d632dfd945224e5e5 using the supplied event base e63be3e7f92f64f7e095131a6ec6b39442ea3dda. Fetched PR #239 into the isolated worktree and verified HEAD before reviewing. Review focused on merge resolutions and fork integrations.

One reproduced P2 finding is inline. Validation: the five focused automation/composer test files passed (88 tests), and the fork guard suite passed (627 tests; some overlap with the focused files). A temporary regression probe using the existing GitHub automation fixture failed because the deferred run used the wrong project, root workspace, and no PR context. The probe was removed afterward. No browser verification or repository-wide checks were run.

yield* releaseFailedDelivery(entry.delivery_id);
} else {
yield* sql`UPDATE scheduled_task_webhook_deliveries SET outcome = 'accepted', error = NULL, rendered_prompt = ${rendered.prompt.slice(0, WEBHOOK_DELIVERY_LOG_PROMPT_LIMIT)}, missing_fields_json = ${encodeMissingFieldsJson(rendered.missing)} WHERE delivery_id = ${entry.delivery_id}`;
if (!(yield* runWebhookDelivery(task, entry.delivery_id, rendered.prompt)))

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[P2] Preserve GitHub PR routing in the deferred dispatch path

The upsert schema accepts schedule.github together with dedupe.coalesceKey, so a valid GitHub automation can reach this worker. Unlike the immediate path, it renders only task.prompt and calls runWebhookDelivery without the GitHub trigger. That drops the exact PR URL/commit context and makes runTask fall back to the task's project/workspace instead of the event's repository and isolated worktree. I reproduced this with a task rooted in project d2, watching owner/no3y, and coalesceKey: "{{body.pull_request.number}}" with a 10-second window: PR #108 launched in d2 with { type: "root" }, and its prompt contained no PR URL. Reconstruct/preserve the GitHub trigger and context for deferred deliveries too, or reject this combination when saving the task.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Confirmed. Upstream's coalescing path (pingdotgg#22234), new in this sync, renders only task.prompt and calls runWebhookDelivery without the GitHub trigger. The fork UI can't set dedupe on a GitHub task, but a stored dedupe survives conversion, and RPC and MCP callers can send both fields. Proposed fix: strip dedupe when a GitHub task is saved. This one is held because it changes server behavior; see the triage comment.

Upstream's new budget test pins the MCP tool list at its own size; the fork's
GitHub PR automation adds 342 bytes to schedule_task, so the test failed.
The budget now records the fork's measured size, and the budget gate compares
a PR with its base branch (custom) instead of main, the upstream mirror,
which would otherwise fail every fork PR. Folded into ci-on-custom with a
guard row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@NoahHendrickson NoahHendrickson left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Code review: 51b3c52

Reviewed commit 51b3c5201d7bcc668efadd4d632dfd945224e5e5. The PR head has since moved to 0838852580, but that commit only touches CI and none of the files below.

Scope. I didn't re-review upstream code taken as-is. I covered:

  • the merge's conflict resolution (git show --remerge-diff 7f237e4c0d, 204 files)
  • re-homed fork fences, compared with the pre-sync fork (47a31babbf / e63be3e7f9)
  • f443c22654 and 51b3c5201d

Every finding was checked against the code at the reviewed commit. Nothing was run: no tests, typecheck or app. GitHub's file list stops at 3,000 files, so findings are linked here rather than posted inline.

High

1. The permissions chip crashes the composer whenever it shows. ComposerFooterControls.tsx#L104-L114

  • Upstream turned the permissions chip from a <Select> into a <Menu>. The resolution kept the fork's ComposerRuntimeModeTrigger as the MenuTrigger render element.
  • That component renders ComposerSelectControl, which is SelectPrimitive.Trigger (ComposerShell.tsx#L211-L224). Nothing above it is a Select.Root any more.
  • Base UI 1.5.0's useSelectRootContext() throws SelectRootContext is missing in that case.
  • Failure: the chip renders whenever the runtime mode isn't the default, or the model has no effort menu (composerControlsOwnership.ts). In either case the composer throws during render. Upstream's ComposerPermissionsSection.browser.test.tsx renders this control, so Test Web should go red too.
  • Fix: build the fork's chip on a non-Select control, e.g. ComposerControl with the mode-chip style and the data-fork-composer-mode-chip attribute.

2. The side chat window now runs every app-wide service. main.tsx#L43

  • Upstream moved the root coordinators into startAppServices() (appInit.tsx): thread notifications, slow-RPC, host-freeze and permission toasts, the update launch notice, installChatRouteCommands, workspace focus and more.
  • main.tsx calls it unconditionally. The fork's isSideChatSurface branch in __root.tsx now only skips JSX that no longer holds those coordinators.
  • Failure: with the side chat window open, every finished turn notifies twice (sound and badge), and app-wide notices appear in the side chat too. The fence comment at __root.tsx#L192-L195 exists to prevent exactly this.
  • Fix: gate the main-window-only services on !isSideChatSurface.

3. Toasts show twice in the side chat window. __root.tsx#L196-L209

  • RootRouteView now wraps every branch in ToastProvider/AnchoredToastProvider (L142-L146). The re-applied side-chat branch still adds its own pair.
  • Both providers bind the same module-level toastManager, and each renders its own viewport (toast.tsx#L544-L548).
  • Failure: every toast in the side chat appears twice, stacked.
  • Fix: drop the inner providers.

4. "New worktree" no longer leaves an existing worktree. useComposerSelection.ts#L231-L237

  • The re-homed fence tests the stored draftThread?.envMode !== "worktree". Before the sync it tested the effective env mode, which reads as "local" for a draft with a worktreePath.
  • "Use previous worktree" (BranchToolbar.tsx#L680-L687) stores envMode: "worktree" with a worktreePath. The fence also replaced upstream's mode === "worktree" && draftThread?.worktreePath ? { worktreePath: null } clear, so neither guard is left.
  • Failure: in a draft, pick "Use previous worktree", then "New worktree". Nothing is cleared, and the thread starts in the old worktree on its branch instead of a fresh one.
  • Fix: also clear when draftThread?.worktreePath is set, and update forkNewWorktreeDefaultBase.test.ts, which pins the current string.

5. A multi-model send loses design-change pills on failure and can send them twice. useComposerSend.ts#L1131-L1151

  • Before the sync, the pills were detached when the draft cleared and restored by restoreFailedDraft when a target failed. Now forkDesignChanges.detach runs only after await sendToMultipleModels(...) finishes, and runs unconditionally. useMultipleModelSend.ts has no restore.
  • Failure: a target fails. The prompt text comes back, and the trailing detach then clears the pills. The "Restore prompt" toast path loses them the same way.
  • Failure: a second send while threads are starting. useMultipleModelSend calls resetLocalDispatch() before await starts, so the composer is free while the pills are still pending. A send in that window carries the same <design_change_request> blocks again, which is what the fence comment says must not happen.
  • Side effect: the old multi-model restore landed in the single-send failure path (L1431-L1433). There it does nothing, except after rekey, where it re-adds the entries under the deleted bootstrap thread's key.

Medium

6. "Open automation" on a PR-review run opens the generic webhook editor. ScheduledTasksSettings.tsx#L400-L403

  • Upstream's rewritten ?taskId= handling calls setEditor directly, skipping the fork's openForEdit. That function sends GitHub PR tasks to /scheduled.
  • Failure: "Open automation" (from TimelineUserMessageRow, QueuedRunsControl or ThreadAutomationsPanel) on a PR-review run opens upstream's webhook editor instead of the Automations page.
  • Fix: route the linked task through openForEdit.

7. Find in a split pane can lose its timeline. threadFindStore.ts#L56-L68

  • Upstream's store keeps one global attachedTimeline. Each pane's timeline re-attaches whenever rows, isWorking or timelineEntries change (useThreadFindPositionReader.ts#L89).
  • Failure: with pane B streaming, Cmd+F in pane A finds timelineFor(A) === null. Live-follow isn't released, and the search ignores A's viewport position.
  • Before the sync, each ChatView had its own reader. The fork's old per-pane fence in useThreadFind.ts was dropped and has no replacement.

8. "Compact idle threads: Never" no longer hides the resume-compaction card. ChatView.tsx#L2601-L2617

  • Upstream pingdotgg#23037 (in this range) gated the resume-compaction offer on resolveIdleCompaction(settings, instance) !== "off". The fork's useResumeCompactionBanner replaced that expression and doesn't read the setting.
  • Failure: with the setting on Never (globally or for one instance), the "Resume with less context" card still appears.

9. Automation runs inflate the new "Settled (N)" count. Sidebar.tsx#L2748

  • The server-paged older-settled query (ProjectionStore.listOlderSettledThreads) excludes archived threads and subagents, but not threads with a scheduledTaskId. The fork filters those out client-side (isScheduledRunOffSidebar).
  • Failure: "Settled (N)" and "Show N more" count runs that never render. useOlderSettledShelfGrowth also keeps paging while the filtered list stays short, so a heavy PR-automation user can page through their whole run history to show nothing.

10. The startup-recovery window points fork users at upstream releases. DesktopStartupRecovery.ts#L21

  • This file is new from upstream and hardcodes https://github.com/pingdotgg/t3code/releases.
  • Failure: "Reinstall" opens pingdotgg/t3code/releases/tag/v<fork version>. That's a 404, or a real upstream release, so the user can end up installing upstream T3 Code over no3y Code. The copied error text also says "T3 Code".

Low

11. The fork's Settings nav styling no longer applies. SettingsSidebarNav.tsx#L417

  • Settings now renders in a portaled AppRouteModal. Every [data-fork-chrome-row] rule (theme.custom.glass-surfaces.css, theme.custom.palettes.css) requires a [data-sidebar-version="v2"] ancestor, and the modal has none. So the Glass active pill and the row pill shape no longer apply in the Settings nav.
  • useForkSettingsMarker still sets [data-fork-settings] on :root. Settings is now a modal over the chat, so the settings-button restyle also hits the chat's outline buttons behind it.

12. GitHub PR automation builds a second GitHub sign-in stack. GitHubPrAutomation.ts#L221-L228

  • GitHubRest.layerWithDependencies creates its own GitHubAppSignIn, whose refreshLock is separate from the driver's.
  • Failure: if the app token has expired and a webhook connect/remove overlaps a driver read, both can post the same single-use refresh token. The loser gets bad_refresh_token. If it runs replaceSession(host, session, null) before the winner saves, it signs the user out.
  • The window is narrow. The comment at L221-L222 also contradicts the two lines after it.

Notes, not regressions

  • .fork/customizations.yaml still describes git-config-lock-retry code that upstream's own retry replaced. The entry has zero fences, and no guard catches that.
  • Upstream's test purge removed several fork identity assertions: appId, product name and protocol name in build-desktop-artifact.test.ts; the Linux desktop entry name; setName "no3y Code Beta". forkAppIdentity.test.ts still pins the product name and profile.

Checked and clean

  • 51b3c5201d is correct: BackgroundThreadContext gates the digits and dictation, a preparing dictation is cancelled, and the focus-effect deps match pre-sync.
  • f443c22654 only changes comments.
  • Split-pane focused gates in ChatView, the thread-state cards and liveness inputs in useThreadLifecycle, the timeline row fences, Appearance palette rows, and the composer fork fences (other than #1) all moved correctly.
  • The ScheduledTaskService webhook claim and release flow is correct.
  • Desktop identity is intact: profile t3code-fork-v2, app id, WMClass, ~/.t3-fork.
  • No migration collisions.
  • The lint deferral scope doesn't ungate upstream files beyond Sidebar.tsx and SidebarChrome.tsx, which were already in it.

Claude Opus 5.5 in Claude Code (T3 Code).

…ault mode

Upstream moved the permissions chip from a Select to a Menu. The fork's
ComposerRuntimeModeTrigger still rendered a Base UI Select.Trigger, which
throws without a Select root, so any thread whose mode differed from the
default crashed the composer. It now renders upstream's ComposerControl.

Upstream's ScheduledTasksSettings case for a user-opened New task editor
racing a late-loading link cannot happen in the fork: New task opens the
fork's /scheduled page. The case is fenced and replaced with one pinning that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

⚠️ This PR changes transfer ceilings:

  • Codex Live turn WebSocket decoded: 29.3 KiB → 24.4 KiB
  • Claude Live turn WebSocket decoded: 29.3 KiB → 24.4 KiB
Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB +68 B (+1.3%) 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.3 KiB +68 B (+5.6%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 20.9 KiB 21.6 KiB +715 B (+3.3%) 24.4 KiB ✅
Codex Live turn messages 2 2 0 (0.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB +61 B (+1.2%) 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.3 KiB +61 B (+5.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.2 KiB 21.9 KiB +727 B (+3.3%) 24.4 KiB ✅
Claude Live turn messages 2 2 0 (0.0%) 8 ✅

Baseline: 6ec3228 · PR result: c740366 · 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.

Review of the sync found fork behavior lost where upstream moved code:

- Side chat: upstream starts every app-wide service from main.tsx, so the
  side chat window doubled notifications, sounds and toasts; it now stops
  after workspace focus and skips main-app location tracking. Its root no
  longer nests a second toast provider pair.
- New worktree from a previous worktree's draft clears the stored path again.
- Multi-model sends detach and restore design-change pills with the cleared
  draft (in useMultipleModelSend), not after the await, so a failure keeps
  them and a send during thread start cannot repeat them.
- Find keeps one attached timeline per thread, so a streaming split pane no
  longer steals the focused pane's.
- "Compact idle threads: Never" hides the resume card, as upstream's setting does.
- "Open automation" on a PR-review task opens /scheduled, like a list click.
- Startup recovery links to the fork's releases.
- Glass Settings nav rows and buttons match inside the new Settings modal,
  and the Settings button restyle no longer reaches the chat behind it.
- One useOwnsGlobalInput() hook for fork window listeners; the stale
  git-config-lock-retry manifest entry (replaced upstream) is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NoahHendrickson

Copy link
Copy Markdown
Owner Author

Triage of the three reviews on 51b3c52. Each finding was checked against the pre-sync fork (47a31ba) and upstream (fe219c4) before acting.

Fixed

Held, needs a server change (decision pending with the maintainer)

Not acted on

  • Review 1, finding 2 (the lint baseline at 876): agreed it no longer gates that rule. Burning it down is a separate change, not part of this sync.
  • The dropped identity assertions in build-desktop-artifact.test.ts (nightly name, installer title, protocol names) are noted for a follow-up guard. They're not a behavior regression.

…e GitHub sign-in

Upstream's new webhook coalescing path dispatches without the GitHub trigger,
so a PR-review task that kept a stored dedupe launched in the task's own
project with no PR context. Saving a GitHub task now drops dedupe; its
deliveries are already one per PR event.

Upstream builds GitHubRest only inside its GitHub driver, so the automation
built a second GitHubAppSignIn with its own refresh lock. The source-control
layer now builds GitHubRest first in the server's memo map, which the
driver's forked map and the automation both reuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NoahHendrickson

Copy link
Copy Markdown
Owner Author

Update on the held server items. Fixed in b9241ac:

  • P2, deferred dispatch: saving a GitHub task now drops dedupe. A row in GitHubPrAutomation.test.ts fails without the fix.
  • feat(fork): swap the web typeface to Geist and Geist Mono #12, second sign-in: builtInDrivers.ts builds GitHubRest.layerWithDependencies first in the server's memo map. The driver's withOwnServices forks that map, and a forked map reads its parent's built layers, so the driver and the automation share one GitHubAppSignIn.

Still held:

@NoahHendrickson

Copy link
Copy Markdown
Owner Author

#9 (Settled (N) counts automation runs): left as a follow-up, per the maintainer. On web the count reads slightly high, and mobile is unchanged. An exact fix needs a separate run count from the server.

@NoahHendrickson NoahHendrickson left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Review of the upstream sync: 5 bugs, 2 fork-rule issues (the deleted manifest entry and an unfenced edit), 3 cleanups.

I'd fix these before merging:

  • Old GitHub PR-review tasks with a stored dedupe still drop PR context (ScheduledTaskService.ts).
  • The git-config-lock-retry manifest entry was removed instead of ported.
  • The Glass Settings button style misses portaled dialogs.

This PR has more than GitHub's 3,000-file inline-comment limit, so the findings below are on files that can't take inline comments.

apps/web/src/theme.custom.palettes.css:780

Bug: the Glass Settings button style no longer reaches portaled dialogs.

The rule now needs a [data-app-modal] ancestor. Settings' nested dialogs and popups portal to <body>, outside the modal popup, so their dark:bg-input/32 buttons lose the Glass look. Portaled content is why useForkSettingsMarker stamps the root in the first place.

Repro: on Glass, open Settings, then Scheduled tasks, then edit a task (or open any confirm dialog from Settings). The outline buttons in the portaled dialog use the upstream style, while the ones in the modal behind it are glass. The same scoping is in theme.custom.glass-surfaces.css (~511).

apps/web/src/theme.custom.glass-surfaces.css:511

Same scoping problem as theme.custom.palettes.css ~780: content portaled out of the Settings modal doesn't match [data-app-modal], so it misses this rule.

apps/web/src/components/chat/useComposerStash.ts:693

Cleanup: this PR adds useOwnsGlobalInput() (focused pane, and not behind Settings or Usage), but this check and ChatView's useChatShortcuts / useOpenFavoriteEditorShortcut checks still combine !backgroundThread && splitPane.focused by hand. The same rule now lives in four places in two forms and will drift. Consider using the helper here.

apps/web/src/custom/voice/useForkDictationController.ts:146

Cleanup: this effect repeats settleWithoutControls (stop if recording, cancel if preparing). If one copy gains a phase such as transcribing and the other doesn't, losing focus will handle a live session differently from controls unmounting. Call settleWithoutControls() here instead.

apps/web/src/components/chat/useComposerSelection.ts:234

Cleanup: draftThread?.envMode !== "worktree" || draftThread?.worktreePath works out resolveEffectiveEnvMode again, where an existing worktree reads as "local". Before the sync this code used the effective envMode directly. If upstream changes how a draft with a worktreePath resolves, this "only on the way in" check will drift and a reselect will clear the chosen base again. Use the shared resolver instead.

🤖 Generated with Claude Code

@@ -1173,8 +1273,11 @@ export const layer = Layer.effect(
secret,
});
});
// Deliveries are already one per PR event, and the coalesced path dispatches
// without the PR trigger, so a GitHub task never keeps a stored dedupe.
const { dedupe: _dedupe, ...withoutDedupe } = schedule;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Bug: GitHub PR-review tasks saved before this PR still drop the PR trigger.

This only removes dedupe when a GitHub task is saved. Delivery (~line 2174) still decides with schedule.dedupe === undefined. A PR-review automation that was saved with a dedupe before this PR takes the coalesced path. That path dispatches without githubEvent, so the review thread starts in the task's own project with no PR context, and it keeps doing that until someone re-saves the task.

Suggested fix: treat schedule.github as no-dedupe at delivery time as well. That covers tasks already stored too.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 8f55cb7. Delivery now decides with const dedupe = schedule.github ? undefined : schedule.dedupe, so a GitHub task stored with a dedupe before the save-time strip also takes the direct path with its PR trigger. New case in GitHubPrAutomation.test.ts plants a stored dedupe in schedule_json and checks the launch lands in no3y with the PR URL and a worktree. It fails without the fix.

Comment thread .fork/customizations.yaml
verify:
- apps/web/src/__fork_guards__/forkImageAttachmentNoChip.test.ts

- id: git-config-lock-retry

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fork rule: the git-config-lock-retry manifest entry was deleted.

.fork/AGENTS.md (Never): "Remove fork:begin/fork:end fences, … or manifest entries — even if the surrounding code moved. Port them."

Upstream's withSharedConfigLock only serializes writes inside the T3 server process. The fork's jittered retry also waited out writers outside the process, such as an agent or another git process holding .git/config.lock while T3 writes branch.<name>.gh-merge-base on first push. With this entry gone, those pushes fail right away with config_locked, and no guard or manifest row flags that the behavior is gone.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Not restoring this one. Upstream pingdotgg#25700 adopted the retry this entry was waiting for, and the entry's own intent said "drop this fence if it adopts a retry". The claim that out-of-process holders now fail right away does not hold. retryWhileConfigLocked (GitVcsDriverCore.ts ~3399) retries config_locked 10 × 50 ms, and its comment names that case: "git run outside T3, such as an agent's, can hold it". withSharedConfigLock is a separate, extra in-process serializer. The push-path gh-merge-base / t3-merge-base-ref writes (~2676) go through writeConfigValue, which retries. The other parts of the fork intent are upstream too: LC_ALL=C in executeGitWithStableDiagnostics and the "File exists" pattern. No fork code is left, so there is nothing to port. A zero-fence entry would only describe upstream behavior.

hidden:
pendingUserInputs.length > 0 ||
phase === "running" ||
resolveIdleCompaction(

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Bug: the fork's resume-compaction card skips two of upstream's suppressions.

It took upstream's resolveIdleCompaction === "off" check, but not !hasHeldQueuedRuns or multipleModelSelections === null. On a thread with held queued runs or an active multi-model selection and an expired cache, upstream deliberately offers no compaction. The fork card still offers Compact, and compacting then runs ahead of, or against, the held queue.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 8f55cb7. The card's hidden now also covers hasHeldQueuedRuns and multipleModelSelections !== null, next to the resolveIdleCompaction === "off" check, which matches upstream's suppressions.

Comment thread apps/web/src/appInit.tsx
/* fork:begin fork-side-chat — see .fork/customizations.yaml#fork-side-chat
The side chat window is only a chat; the main window runs everything below once, so a
second copy would double notifications, sounds and app-wide toasts. */
if (isSideChatSurface) return router;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Bug: the side chat window starts app-wide subscriptions a second time.

This early return comes after startAgentOpenedPreviews, startPreviewTitleRecording, subscribeDraftThreadPromotion, subscribeComposerDraftModelReconciliation and installChatRouteCommands. Each of these covers every thread and environment, and the main window already runs them. With the side window open, agent-opened previews and preview-title recording run in both windows, which doubles the work and can open preview state in the side window. Before this sync the work was per-ChatView, so the side window only covered its one thread. Moving the return above these calls would fix it.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 8f55cb7, more narrowly than suggested. startAgentOpenedPreviews and startPreviewTitleRecording are now gated on !isSideChatSurface, because both follow every thread with previews and the side chat shows no panels. I left the early return where it was. The other three ran in the side chat before this sync too: chat-route shortcuts from the _chat layout, draft model reconciliation from AppRoot, and draft promotion acting on the window's own write-isolated draft store. Moving the return above them would take those away from the side chat. Guarded in forkSideChat.test.ts.

Comment thread apps/web/src/components/ChatView.tsx Outdated
});
};
const scrollToEndStable = useStableCallback(scrollToEnd);
// fork-split-threads: splitPane.focused is read, not a dependency. Focus

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fork rule: unfenced fork edit in an upstream file.

The fork-only comment and the oxlint-disable-next-line have no fence. .fork/AGENTS.md: "inline edit of an upstream file → keep the hunk small, fence it." Fence audits (grep -a fork:begin) can't see this, so the next sync can take upstream's effect and quietly bring back the splitPane.focused dependency this commit removed.

- PR-review automations saved with a dedupe before the save-time strip
  still took the coalesced path and lost their PR trigger. Delivery now
  treats any GitHub task as no-dedupe.
- The fork's resume-compaction card also stands down for held queued runs
  and a multi-model selection, as upstream's offer does.
- The side chat skips agent-opened previews and preview title recording;
  both follow every thread and the window shows no panels.
- Glass Settings buttons match any popup, so dialogs and menus portaled out
  of the Settings modal keep the chip look.
- The stash shortcut, open-in-editor and chat shortcuts read
  useOwnsGlobalInput(); dictation's focus-loss effect and
  settleWithoutControls share one settleSession; the new-worktree fence
  asks resolveEffectiveEnvMode; the split-pane deps comment is fenced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NoahHendrickson

Copy link
Copy Markdown
Owner Author

Triage of the Oct 30 review on b9241ac. Fixes are in 8f55cb7. The inline items have replies on their threads, except the ChatView.tsx:2193 one, where GitHub kept rejecting the reply, so it is here.

Fixed

  • Stored dedupe on GitHub tasks (ScheduledTaskService.ts): delivery treats any GitHub task as no-dedupe, which covers tasks saved before the strip. A regression case fails without the fix.
  • Resume-compaction card: it also hides for held queued runs and a multi-model selection.
  • Side chat app services (appInit.tsx): agent-opened previews and preview title recording are gated off in the side chat. The early return stays where it is, because chat shortcuts, draft promotion and draft model reconciliation also ran in the side chat before the sync.
  • Unfenced edit (ChatView.tsx:2193): the deps reason sits inside the disable directive with an inline fork:begin … fork:end fork-split-threads pair, the same form as TimelineV2EventRow.tsx:68.
  • Glass Settings buttons in portaled dialogs (theme.custom.palettes.css ~780, theme.custom.glass-surfaces.css ~511): the five rules are scoped to [data-slot$="popup"] instead of [data-app-modal]. The Settings modal is itself a dialog-popup, and its nested dialogs and menus portal to <body> as popups. The chat behind the modal is inert while it is open, so nothing there matches. SettingsSidebarNav already uses [data-slot$="popup"] as the "any popup" test. This is a CSS-only change and hasn't been checked in the Glass desktop app yet.
  • useOwnsGlobalInput everywhere (useComposerStash.ts, and ChatView's open-in-editor and chat shortcuts): the fenced token reads the helper now. Upstream's own !backgroundThread stays in place, so the fences stay one token wide.
  • Dictation settle (useForkDictationController.ts): one module-level settleSession(controller) serves both the focus-loss effect and settleWithoutControls.
  • New-worktree fence (useComposerSelection.ts): it asks resolveEffectiveEnvMode with the draft's own worktreePath and envMode, as it did before the sync.

Not changed

  • git-config-lock-retry manifest entry: upstream's retryWhileConfigLocked (10 × 50 ms) retries config_locked from writers outside the process too, and the push-path merge-base writes use it. LC_ALL=C and the "File exists" pattern are upstream as well. The entry asked to be dropped once upstream adopted a retry, and no fork code is left to port. The full reasoning is on the inline thread.

Checks run: the GitHubPrAutomation and ScheduledTaskService tests, the fork guard suite (630 tests), web and server typecheck, targeted lint, and .fork/lint-owned.mjs.

Claude Opus 5.5 in Claude Code (T3 Code).

xxxxxxxxxxxxx and others added 6 commits October 30, 2026 11:55
… after the sync

Upstream's simpler composer (pingdotgg#25869) changed three things the fork leaned on
without a conflict:

- composer-host paints its own glass ::before when a context row is present,
  which drew a dark tray behind the fork's vessel. It joins the shell
  pseudo-elements the fork already switches off.
- The shell's context marker is data-context-row now, not data-with-context,
  so the 8px gap between the chips and the vessel never applied.
- The chips' and the model trigger's carets went with ComposerControlChevron.
  The worktree and branch chips and the fork's model picker shadow draw them
  again.

Guards in forkComposerShell.test.ts pin all three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… PR review

A GitHub task saved with a dedupe before the save-time strip still passed its
replay key to the immediate path, so the same PR number in a second watched
repository read as a duplicate. Delivery hands that path the task without its
stored dedupe; capacity and rate reservations still apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fork lint gate holds no-low-value-comments at its 876 baseline, and the
"(pingdotgg#25869)" reference made it 877, failing Lint on the sync PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #240 (landing draft cleanup) and #241 (design mode fixes).
Upstream moved the right panel's width hook from ChatView into
ChatRightPanel, so design mode's panel-room floor now reaches it through
a fenced minWidth prop; manifest watch entry and guard follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The landing-draft header and a guard header were over four lines, the
panel-room effect had no stated reason, and the boot browser test cleaned
up in afterEach instead of a disposable. All four tripped the lint-owned
ratchet once custom merged in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NoahHendrickson pushed a commit that referenced this pull request Oct 31, 2026
Takes the upstream sync (#239) ahead of it landing on custom. The one
conflict is the draft overlay in ChatView: both the Space Home stamp and
the new-agent-draft className fence stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NoahHendrickson
NoahHendrickson merged commit 44570d6 into custom Oct 31, 2026
28 checks passed
@NoahHendrickson
NoahHendrickson deleted the claude/sync-2026-10-28 branch October 31, 2026 03:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 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.