From 0bbe2ab048bb14ba46c6409bf1cbb49962ec973b Mon Sep 17 00:00:00 2001 From: tzioup <166889479+tzioup@users.noreply.github.com> Date: Thu, 3 Sep 2026 00:38:50 +0200 Subject: [PATCH 1/2] docs: document the fork customization workflow in SELF_UPDATE The branch-and-rebase workflow for fork users lived only in the UpdateTab fork panel, which renders only when `isFork` is true and which a fork user sees only if they open that tab. SELF_UPDATE.md covered the update mechanism (pull, the switch to main, pre-checkout stashing) but never the workflow it implies. Writes the loop out under a new "Running a customized fork" section: keep main a clean mirror, private work on a branch, rebase after each sync, and then reinstall, rebuild and restart, which update.sh cannot do because it always finishes on main. Notes that install order matters, since npm install rewrites client/package.json and re-stales an earlier build. Also documents a confusing side effect nobody had written down: a clean rebase rewinds and rolls forward the working tree, so every touched file gets a new mtime and getInstallState reports a stale build plus stale deps in all four workspaces even though no dependency changed. Co-Authored-By: Claude Opus 5 (1M context) --- docs/README.md | 2 +- docs/SELF_UPDATE.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 28bdc68dc5..b5cdfdfdc6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,7 +25,7 @@ Index of everything under `docs/`. Start with the [root README](../README.md) fo | [CONTRIBUTING.md](./CONTRIBUTING.md) | Dev setup (PostgreSQL required), code conventions | | [GITHUB_ACTIONS.md](./GITHUB_ACTIONS.md) | CI and release workflows | | [VERSIONING.md](./VERSIONING.md) | SemVer + release process (`/do:release`) | -| [SELF_UPDATE.md](./SELF_UPDATE.md) | Fork-aware self-update flow — release polling, `FORK_SYNC_REQUIRED`, fork sync | +| [SELF_UPDATE.md](./SELF_UPDATE.md) | Fork-aware self-update flow — release polling, `FORK_SYNC_REQUIRED`, fork sync, running a customized fork | | [MANAGED_APP_UPDATES.md](./MANAGED_APP_UPDATES.md) | Safe managed-app update default and the opt-in app lifecycle contract | | [DEPS.md](./DEPS.md) | Dependency audit — every third-party package and its verdict | | [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) | Common runtime issues, known issues | diff --git a/docs/SELF_UPDATE.md b/docs/SELF_UPDATE.md index 0998abfd2b..350eb52e5d 100644 --- a/docs/SELF_UPDATE.md +++ b/docs/SELF_UPDATE.md @@ -105,6 +105,39 @@ When `isFork` is true, `UpdateTab` replaces the single "Update Now" button with Keep these three behaviors distinct. Collapsing them strips the user's agency over what touches their GitHub fork. +## Running a customized fork + +The `UpdateTab` fork panel states this in short form. The full loop is here, because that panel is +the only other place it exists and it renders only when `isFork` is true. + +Keep `main` a clean mirror of upstream and never commit to it — that is what keeps `gh repo sync` +fast-forward and avoids 409 `FORK_DIVERGED`. Private changes live on their own branch, rebased +onto `main` after each sync. Anything shareable goes upstream as a PR instead, so you carry less +forward each time. + +**PM2 boots whatever is checked out**, so the branch you are on is the code that runs. Staying on +your private branch is what makes your customizations live; there is no separate step. + +### After rebasing, reinstall and rebuild + +`update.sh` always finishes on `main`, so it cannot do this half for you: + +```bash +git checkout main && ./update.sh +git checkout && git rebase main +for d in . client server autofixer; do (cd "$d" && npm install); done +npm run build && npm run pm2:restart +``` + +Order matters. `npm install` rewrites `client/package.json`, so building before installing leaves +the build stale again. + +**A clean rebase makes the install look broken.** Checking out a branch based on an older `main` +rewinds the working tree, and the rebase rolls it forward, so every touched file gets a new mtime. +`getInstallState()` (`server/services/installState.js`) compares mtimes, so it reports a stale +build and stale deps in all four workspaces even though no dependency changed and no build input +was edited. The reinstall and rebuild above clear it. + ## Image-bearing Persistent Mind work must drain before source transitions The managed update route refuses to restart into a different source revision while a queued Persistent Mind message or active turn carries image references. `GET /api/update/status` reports the privacy-safe `persistentMindImages` preflight (`safe`, queued count, and active-turn boolean), and `POST /api/update/execute` re-checks it before and after acquiring the update lock. From 264d2340a2eea61da20ae5ee3e461b828dfad349 Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" Date: Thu, 3 Sep 2026 08:41:07 -0700 Subject: [PATCH 2/2] docs: prescribe --no-save installs and the trusted-rebuild step in the fork loop The manual reinstall loop told fork users to run a bare `npm install`, which is not what PortOS installs with anywhere. `safe_install` in update.sh and scripts/ensure-deps.js both pass `--no-save`, deliberately: a bare install rewrites client/package.json and the lockfiles, dirtying a fork's private branch and re-staling the build the next line asks for, and an older npm strips newer lockfile metadata it does not understand. The loop also omitted `node scripts/trusted-rebuilds.js server`. Every workspace .npmrc sets `ignore-scripts=true`, so following the loop as written left node-pty unbuilt and the shell/TUI features crashing on a missing binding. Also pairs update.ps1 with update.sh, matching every other mention in the file. --- docs/SELF_UPDATE.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/SELF_UPDATE.md b/docs/SELF_UPDATE.md index 350eb52e5d..3b407df194 100644 --- a/docs/SELF_UPDATE.md +++ b/docs/SELF_UPDATE.md @@ -120,17 +120,27 @@ your private branch is what makes your customizations live; there is no separate ### After rebasing, reinstall and rebuild -`update.sh` always finishes on `main`, so it cannot do this half for you: +`update.sh` / `update.ps1` always finish on `main`, so they cannot do this half for you: ```bash -git checkout main && ./update.sh +git checkout main && ./update.sh # .\update.ps1 on Windows git checkout && git rebase main -for d in . client server autofixer; do (cd "$d" && npm install); done +for d in . client server autofixer; do (cd "$d" && npm install --no-save); done +node scripts/trusted-rebuilds.js server npm run build && npm run pm2:restart ``` -Order matters. `npm install` rewrites `client/package.json`, so building before installing leaves -the build stale again. +**Use `--no-save`, not a bare `npm install`.** `--no-save` is what `safe_install` in `update.sh` +and `scripts/ensure-deps.js` run, for two reasons that both bite a fork: a bare install rewrites +`client/package.json` and the lockfiles, which dirties your private branch and re-stales a build +you just made (`client/package.json` is a `staleBuild` input — see below); and an older npm can +strip newer lockfile metadata it does not understand. `--no-save` still honors `package-lock.json`. + +`scripts/trusted-rebuilds.js` is not optional for the server. Every workspace `.npmrc` sets +`ignore-scripts=true`, so the server's native addons (`node-pty` and friends) are never built by +the install itself — skip the rebuild and the shell and TUI features crash on a missing binding. + +Order matters: install before you build, so the build sees the deps it compiles against. **A clean rebase makes the install look broken.** Checking out a branch based on an older `main` rewinds the working tree, and the rebase rolls it forward, so every touched file gets a new mtime.