Skip to content

view shell live code: repoint already-bound symbol pointers, so a patch changes what draws - #35850

Merged
teamleaderleo merged 1 commit into
feat-cmux-nextfrom
lane/live-code-rebind-pointers
Nov 4, 2026
Merged

teamleaderleo merged 1 commit into
feat-cmux-nextfrom
lane/live-code-rebind-pointers

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Leo (2026-11-04): "Couldn't we architect stuff to make everything more amenable to hot swappage?"

The view shell's live code never changed what it drew. In the headless measurement (scripts/measure/live-code-shell.sh), every generation's badge was the same picture, grey with padding 8. Each patch loaded and replaced its functions (functions=4), but the drawing still ran the build's code. The measurement's "second patch redrew: yes" came from cmp on PNG bytes (2,036 vs 2,044 bytes for the same pixels).

Why. The executable is linked -interposable -no_fixup_chains, so its calls to its own Swift functions go through classic symbol pointers. One of them is __la_symbol_ptr for PreviewBadge.body (dyld_info -fixups: lazy-bind PreviewBadge.body). Generation 0 draws before any patch, so by then dyld has bound that pointer to the build's body, and dyld_dynamic_interpose does not rewrite a pointer that is already bound.

Fix. After the existing dyld_dynamic_interpose call, Interposer.rebind writes the patch's address into every __la_symbol_ptr and __got slot of the executable that names a replaced function, as fishhook does. __DATA_CONST is made writable for the write. The existing interpose call stays as it is.

Measured on a fleet Mac at this fix plus #35705. inject.sh was the only other change, to tar the PNGs out. The badge's rendered width, at 2× scale:

generation edit before this fix with it
0 none 135 px, grey 135 px, grey
1 padding 8 → 24, red 135 px, grey 199 px, red (+64 = 2 × 16 pt × 2)
2 padding 24 → 40 135 px, grey 263 px, red (+64)
3 a new file's green dot (#35705) 135 px, grey 283 px, red, with the green dot (+20 = (6 + 4) pt × 2)
4 a new @State refused refused

I checked the PNGs by eye as well. Edit to pixels: 1.45 s for the first patch and 1.14 s for the next.

Follow-up: the measurement should compare decoded pixels or widths, not bytes. It is a separate PR because the fleet runs only the reviewed bytes of a measurement script.

Part of manaflow-ai/cmuxterm-hq#7066.

🤖 Generated with Claude Code
Agent session: agent session


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes the view shell's live code so a patch actually changes what draws: previously every generation of the headless measurement drew the same badge, because dyld_dynamic_interpose does not rewrite a symbol pointer dyld has already bound (the build's view had been drawn before any patch).

  • Adds Interposer.rebind, which after the interpose call writes the patch's address into every __la_symbol_ptr and __got slot that names a replaced function, as fishhook does.
  • Makes __DATA_CONST writable for the write via mprotect.
  • Measured result: the badge's padding and color changes from a patch now show in the rendered output, and a new file's drawing appears.

Migration

  • None. The executable's existing -interposable -no_fixup_chains linking is required for the rebind to be possible; that is already in place.

Written for commit 267eac1. Summary will update on new commits.

View guided diff Turn on auto-fix

… so a patch changes what draws

dyld_dynamic_interpose does not rewrite a lazy or GOT pointer dyld has already bound, so a view's
body that SwiftUI called before a patch kept running the build's code: every generation of the
headless measurement drew the same badge (the PNGs differed in bytes only). Interposer.rebind
writes the patch's address into each __la_symbol_ptr and __got slot that names a replaced
function, as fishhook does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-45fddab7397278d38551
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 267eac1: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr35850 -f ref=lane/live-code-rebind-pointers -f platform=macos -f backend_mode=local

Shipped in:

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex, router-review): SHIP at 267eac1

Reviewer: codex gpt-6-astra high, fast tier.

Follow-ups: 2. They land as checklist items in this PR's one follow-up tracking issue in this repo (Follow-ups: #N ..., label follow-up; add to an open one before filing, never one issue per finding), linking this receipt; the lane that opened the PR fixes them forward.

SHIP at 267eac1

Visual verdict: no images; Leo tries the build

  • [P2] [follow-up] [category:reliability] [finding:F1] [outcome:pending] [Prototypes/ViewShell/Sources/ViewShell/LiveCode.swift:156] rebind ignores failed mprotect calls and apply ignores its write count, so a patch can report success while some pointers still target old code. Surface failures or verify all required slots were rebound.
  • [P2] [follow-up] [category:ux] [finding:F2] [outcome:pending] [Prototypes/ViewShell/Sources/ViewShell/LiveCode.swift:156] No after screenshot or gallery artifact was available; attach one so the live-render visual claim is reviewable.

Follow-ups:

  • F1: Handle partial or failed pointer rebinding.
  • F2: Provide an after visual artifact.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Pictures from the headless measurement (--live-render, badge at 2×):

picture
before this fix, generation 2 (padding 40, red, expected)
with it, generation 1 (padding 24, red)
with it, generation 2 (padding 40)
with it and #35705, generation 3 (a new file's dot)
Agent session: agent session

@teamleaderleo
teamleaderleo merged commit 4546c99 into feat-cmux-next Nov 4, 2026
137 of 150 checks passed
@teamleaderleo
teamleaderleo deleted the lane/live-code-rebind-pointers branch November 4, 2026 19:59
cmux-bot Bot pushed a commit that referenced this pull request Nov 4, 2026
…ve_apply finds @main among other attributes (#35861)

Review follow-ups of #35850 (rebind ignored a refused mprotect) and #35705 (`@MainActor @main`
was counted live).


Agent-Session: https://cmux.com/deeplink/agent?id=aid-45fddab7397278d38551

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Nov 4, 2026
…G bytes (#35862)

* live-code-shell measurement: judge a redraw by the badge's rendered width, not PNG bytes

The same pixels encode to PNGs of different bytes, so cmp said "redrew" while every generation
drew the build's badge (#35850). Also: refuse to run over an existing probe file, stop waiting on
the new-file step once a refusal is logged, report an inject.sh failure as such, and record the
widths in result.json. Review follow-ups of #35759.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-45fddab7397278d38551

* live-code-shell measurement: refuse a dangling probe link, report step 4's inject failure, name a missing width

Review findings of #35862 at 803b764.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-45fddab7397278d38551

* live-code-shell measurement: restore only this run's Badge.swift backup; check the probe before building

Review finding of #35862 at fd073c8: a reused OUT's older Badge.swift.orig was copied back on exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-45fddab7397278d38551

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Nov 5, 2026
…oof; Debug and bench only) (#36118)

* live code in the app: a patch of CmuxNextApp's Swift shows in a running bench build

Leo, 2026-11-05: "if we're literally just trying to prove a thing works / please don't
frontrun it with belt and suspenders / just get it working / THEN we go"

The proof, before the trust check: the view shell's loader (#35314, #35512, #35850, #35861)
in the app as CmuxNextLiveCode, started only by CMUX_LIVE_CODE_DIR; reload.sh's
CMUX_LIVE_CODE_BUILD=1 builds CmuxNextApp unoptimized and testable and links the app
-interposable with its symbols exported; scripts/live-code/patch.py compiles edited files
with the frontend command of that build; scripts/measure/live-code-app.sh times two patches
of the vault list's background end to end and counts their pixels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-0d93125da077eb6bbe1e

* arch: a reviewed exception for live code's one watcher instance (proof)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-0d93125da077eb6bbe1e

* live code: Observation, and the folder's disk work off the main thread through the checked seams

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-0d93125da077eb6bbe1e

* live code: only Debug and bench builds watch the live folder, until the signed pointer check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-0d93125da077eb6bbe1e

* live code: import Observation publicly (LiveCode's @observable conformance is public)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-0d93125da077eb6bbe1e

* live code: patch AppKit drawing and redraw every window; the app delegate owns the one watcher

No new SwiftUI file (the no-SwiftUI allowlist only shrinks): the proof patches HomeHostView's
updateLayer, and after each patch every window's views are invalidated and drawn. The app
delegate holds the instance, so there is no shared one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-0d93125da077eb6bbe1e

* settings: CmuxConfigFile.launchURL() holds the app's settings-file bootstrap (moved out of AppDelegate)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-0d93125da077eb6bbe1e

---------

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

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 267eac11 Deployed Nov 4, 2026 by teamleaderleo via cmux-tui tree preflight #112906
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:low Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant