Restores the legacy UI of OpenCode Desktop (Windows) by patching the one-line "sunset date" that OpenCode forced on 2026-09-14 to push everyone onto the new UI.
- Method: same-length binary patch over the installed app's
app.asar(no recompiling, no toolchain, no third-party binaries). - Tested on: OpenCode Desktop 1.18.35 (Electron 42), Windows, 2026-10-07.
- Works with: 1.18.34 and 1.18.35 (same literal). The script searches for the pattern dynamically, so it also works on future versions as long as upstream keeps the constant.
OpenCode 2.0+ completely removed the legacy-UI sunset mechanism that this patch relies on:
- The
oldInterfaceSunset = new Date(2026, 8, 14)constant is gone. resolveNewLayoutDesigns,newLayoutDesigns,oldLayoutEligibleand theoldInterfaceSunsettoggle are gone.- There is no legacy-UI switch in v2 (v2 ships a single UI with vertical tabs).
As a result, this patch does nothing on v2: the script's patch mode runs
fail-closed (it requires exactly 1 occurrence of the literal and aborts when it
finds 0), so it will not touch your asar. If you are on v2 and want the old
layout, your options are:
- Stay on v2 and adapt to the new UI (vertical tabs, no persistent project/session sidebar).
- Downgrade to a v1 release (1.18.34 / 1.18.35 / 1.19.x) where the
sunset constant still exists, then re-apply this patch. The last v1 line is
1.19.x; verify the literal is still present before patching. - Community alternatives (not affiliated with this repo):
kuznecov-anatoliy/opencode-old-interface— unofficial fork (base is actually upstream v1.18.30, unsigned binaries).- aa2013's
layoutUpgrade()patch (issue #48882) — an alternative v1-only patch that targetsif (layoutUpgrade()) return true;and replaces it withreturn false;(equal-length, padded with spaces). Same idea as this repo but a different code path; useful if the sunset literal is removed butlayoutUpgrade()remains. - macOS:
restore-old-ui.sh(see issue #48882 comments). - Linux:
perl -0777 -pi -e 's/new Date\(2026,8,14\)/new Date(9999,8,14)/'on the installed binary. openchamber/openchamberandcline/clinedesktop apps as alternative front-ends.
Upstream status: OpenCode 2.0 is out and development is focused on V2; issues filed against V1 are being closed as duplicates of #48882. There is an open community request for a V2-equivalent patch ("Need for V2 patch", #49005), but no working V2 patch exists at the time of writing.
Script: patch-legacy.ps1
# 1) Patch (idempotent; works with the app open or closed)
& ".\patch-legacy.ps1"
# 2) Show status (touches nothing)
& ".\patch-legacy.ps1" -Mode status
# 3) Back to 100% official state (close the app first)
& ".\patch-legacy.ps1" -Mode restoreAfter patching: restart OpenCode. If your newLayoutDesigns preference was already
forced to true (the normal case after the sunset), the old-interface toggle appears in
Settings → enable it. With an eligible profile and an undefined preference, the legacy UI
comes up directly without touching anything.
In packages/app/src/context/settings.tsx (compiled inside the asar):
const oldInterfaceSunset = new Date(2026, 8, 14); // official
const oldInterfaceSunset = new Date(2099, 8, 14); // patched- The literal is searched dynamically; the script requires exactly 1 occurrence (if upstream changes it, the script aborts without touching anything).
- Same-length replacement (2026 → 2099, 4→4 chars): the asar header only stores file sizes, not hashes → the asar stays valid after writing.
- Post-write verification re-reads the file (2026×0, 2099×1).
Why it works: the UI is decided by
resolveNewLayoutDesigns(retired, preference, fallback) = retired ? true : (preference ?? fallback).
With retired=false (date in the future), the user's preference wins and, if it is
undefined, the fallback legacyNewLayoutDesignsDefault = false applies for eligible
profiles → legacy UI by default.
The Electron updater schedules void updater.start() + a check() every 10 minutes in
packages/desktop/src/main/index.ts (autoDownload=false, but it downloads automatically
when a newer version exists). If left alive, it would download a new version and, on
"restart to update", restore the official asar and silently wipe the patch.
Fix: rewrite resources\app-update.yml (outside the asar, not covered by Authenticode)
to point at a nonexistent repo → every check ends in HttpError: 404 → silent error
state (error dialogs only open from the menu/IPC). Evidence after the restart:
updater state changed { from: 'checking', to: 'error' }
Error: HttpError: 404
Result: OpenCode frozen on 1.18.35 until you decide to update.
The first run creates:
%LOCALAPPDATA%\Temp\opencode\legacy-backup-<timestamp>\
app.asar.bak
app-update.yml.bak
-Mode restore reverts both files (it never overwrites a backup with an already-patched
asar: the first backup is kept as the true original).
| Check | Result |
|---|---|
| Version | app starting { version: '1.18.35' } after relaunch ✅ |
| Literal in asar (1.18.35) | new Date(2026, 8, 14) ×1 (confirmed before patching) ✅ |
| After patching | 2026×0, 2099×1 ✅ |
| Hot patch | wrote the asar with the app running → allowed by Windows ✅ |
| Updater | silent 404, no downloads or dialogs ✅ |
opencode.settings |
oldLayoutEligible: true (toggle available) ✅ |
Renderer store (default.dat) |
showCustomAgents: true, layoutTransitionEligible: true; newLayoutDesigns false after the toggle ✅ |
| Legacy UI | active and persistent ✅ |
Safety analysis of the approach:
- Electron fuses:
embedded_asar_integrity_validationdefaults to0in Electron 42 andelectron-builder.config.tsconfigures no fuses → no asar integrity validation. - Authenticode: signs only
OpenCode.exe(untouched) → no SmartScreen or execution impact. - Code cache: V8 validates source content → it recompiles on its own; no need to clear
Code Cache.
⚠️ Do not upgrade to OpenCode 2.0+ if you want to keep using this patch: v2 removed the sunset constant entirely, so there is nothing to patch and the legacy UI is gone. Stay on a v1 release (≤ 1.19.x) to keep the patch working.
# 1. Close OpenCode and go back to official
& ".\patch-legacy.ps1" -Mode restore
# 2. Open OpenCode → Settings → Check for updates → restart
# 3. Patch again
& ".\patch-legacy.ps1"
# If the literal no longer exists (upstream change), the script detects it and touches nothing.
# 4. Restart → Settings → flip the legacy UI toggle if needed- OpenCode 2.0+ is unsupported (see "Compatibility" above): the sunset constant and the legacy-UI toggle were removed upstream, so this patch has nothing to patch on v2. Stay on v1 (≤ 1.19.x) to use it.
- Frozen updates: while the updater is neutralized, no official bugfixes arrive. Update manually with the workflow above.
- Upstream literal: if
new Date(2026, 8, 14)disappears or changes format, the script fails safe (fail-closed) and needs revisiting. - Future integrity fuse: if a release enables
embedded_asar_integrity_validation, the app would not boot with the patched asar → run-Mode restore. - A 1-line patch, not a fork: it does not replace the UI code; it only stops OpenCode from forcing the new one. The legacy UI is the legacy code upstream still ships.
- The 2026-09-14 sunset (
oldInterfaceSunset) forcednewLayoutDesigns=trueon every profile → the previous workaround (default.dat → false) stopped working. - Issue: anomalyco/opencode#38230 (public 👍 vote and comment).
- Community fork
kuznecov-anatoliy/opencode-old-interface: analyzed and rejected (actual base is upstream v1.18.30, 3 minor patches, unsigned binaries). - Build from source (plan C): rejected for cost (bun + toolchain + maintenance).
- Plan D (this repo): patch on top of the installed app → 5 minutes, reversible.
| File | Description |
|---|---|
patch-legacy.ps1 |
Script with patch (default) / restore / status modes |
README.md |
This document |
MIT — use/share at your discretion.