chore: sync fork with upstream v0.0.31 (security fixes + auto permission mode) - #88
Merged
Merged
Conversation
When a pre-launch command (or claude itself) fails fast, the renderer's onProcessExited handler was calling destroySession immediately, which disposes the xterm and removes the DOM element. Any stderr the failing command wrote (claude: command not found, devbox: unknown flag, shell errors, etc.) was destroyed with the terminal, leaving the user with no feedback — a blank terminal opens for a moment and vanishes. Write a styled "── session exited (code N) ──" banner into the terminal and defer the destroy. For Claude sessions the terminal stays mounted so the user can scroll back and read whatever was printed before the exit; re-clicking the session destroys + relaunches via the existing closed- entry branch in openSession. Plain terminal sessions remain ephemeral (destroyed and removed from the sidebar immediately).
A TUI that turns on mouse tracking (CSI ?1002h / ?1003h) makes xterm.js forward every drag to the application, so no text can be selected. Terminal.app and iTerm2 let you hold Option to override that; xterm.js gates the same escape hatch behind macOptionClickForcesSelection, which defaults to false. The symptom is misleading: Cmd+C finds no selection, so xterm's copy handler is a no-op and fails silently, leaving the previous clipboard contents in place. The next paste then inserts stale text and reads as a broken paste. Windows and Linux are unaffected — there the override is Shift, which needs no option.
The push-to-talk handler (#22) intercepted every plain-Space keydown, calling preventDefault() and writing a raw space straight to the PTY. xterm's _keyDown runs the custom key handler BEFORE its composition helper, so during Korean/Japanese/Chinese composition this blocked the commit and reordered/dropped the in-progress syllable (e.g. "녕 " came out as " 녕"). Gate the direct-send path on !isImeComposing(e) (isComposing, or the Chromium keyCode 229 sentinel) so it only fires when no IME is composing — exactly when push-to-talk needs it. During composition the event now falls through to xterm's composition helper for correct commit behavior. Extracts the decision as a pure, exported predicate and adds unit tests. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every display site shortened a project path with
split('/').filter(Boolean).slice(-2), but projectPath comes from a session's
`cwd`, which is backslash-separated on Windows. The split finds no separator,
slice(-2) keeps the single element, and the label renders the entire path:
C:\Users\me\Documents\Vault\Projects\Early_App
instead of "Projects/Early_App". It affects the sidebar headers, the grid
view headings and cards, the new-session dialog, the settings viewer title,
and the plans/memory list — so on Windows nothing is actually shortened.
Adds shortProjectPath() to public/utils.js and routes the renderer sites
through it; main.js gets the same split locally, since it cannot share the
renderer helper.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add pacman target for Arch/Manjaro bundling Linux builds now produce a .pacman package alongside AppImage and deb, installable via `sudo pacman -U`. CI installs libarchive-tools on the Ubuntu runners (provides bsdtar, required by fpm's pacman backend) and uploads dist/*.pacman with the other Linux artifacts. README adds a Building on Arch / Manjaro note: building deb/pacman locally on Arch requires libxcrypt-compat because the bundled fpm links against libcrypt.so.1. * Fix pacman package: deps, name collision, and Linux icon - pacman.depends: drop bogus deb-style defaults (http-parser etc.) and declare real Arch packages (gtk3, nss, libnotify, libxtst, libxss, xdg-utils, libsecret) so 'pacman -U' can resolve. - pacman.packageName: rename to 'switchboard-doctly' to avoid conflict with extra/switchboard (Pantheon Control Center). - linux.icon: point at build/icons/ so electron-builder installs PNGs at standard hicolor sizes (16-512). The single 472x472 source was being installed at hicolor/472x472/apps/ which no desktop environment looks in, so the launcher fell back to a generic icon. generate-icons.js now also produces these sizes. * Polish pacman packaging: artifactName, rename rationale, dedup requires - pacman.artifactName: emit 'switchboard-doctly-<version>.pacman' so the released file matches the installed pkgname. Avoids confusion when users search release assets for the package they have installed. - README: explain WHY the pacman package is renamed (collides with extra/switchboard, the Pantheon Control Center on elementary OS) and document the uninstall command 'sudo pacman -R switchboard-doctly'. - scripts/generate-icons.js: hoist duplicate 'png2icons' and '@napi-rs/canvas' requires to the top. Node caches require(), so this was harmless duplication — just clearer this way.
--worktree creates a fresh isolated git worktree, which only makes sense when STARTING a session. On resume (isNew === false) it makes claude try to spin up a new worktree and fail to attach — so a plain sidebar click, and equally the "Create scheduled task" flow (launchScheduleCreator also resumes via openTerminal with isNew=false), silently broke. Rather than stripping the option at each renderer call site, gate it in the open-terminal handler: only append --worktree when isNew. That closes every current and future resume path in one place, and avoids mutating the caller's options object. (cherry picked from commit 5384778)
…55) Routes terminal copy through the main-process clipboard (the renderer's navigator.clipboard is unreliable on Wayland/Ozone) and wires up OSC 52 so programs in the terminal — including Claude Code — can set the system clipboard. OSC 52 read-back queries are deliberately refused rather than answered, so a session cannot exfiltrate the user's clipboard; decodeOsc52Payload() documents that and test/clipboard-osc52.test.js pins it. Co-authored-by: ymajoros <yannick@valuya.be>
The `windows-latest` label rolled over to Windows Server 2025, whose image no longer ships the Visual Studio C++ toolchain node-gyp needs, so every Windows build failed in electron-builder's install-app-deps while rebuilding node-pty. Pinning to windows-2022 restores an image that still carries the toolchain. Same fix @Flaykz independently included in #72; split out so it lands on its own.
`claude --permission-mode` gained an `auto` choice (CLI 2.1.220 accepts acceptEdits, auto, bypassPermissions, manual, dontAsk, plan). Auto runs a classifier per action rather than pattern-matching on tool type: routine work is allowed, risky actions stop for consent, dangerous ones are blocked. It was not reachable from Switchboard at all. The mode list was copy-pasted three times — twice in dialogs.js and again as hardcoded <option> tags in settings-panel.js. Adding the mode to the dialogs alone would have left the settings dropdown unable to persist it, so a project default of Auto stayed unreachable. Hoisted the list into PERMISSION_MODES in utils.js and drove all three call sites from it; the settings panel now renders its options the same way the theme dropdown directly below it already did. No main-process change: the open-terminal handler already forwards sessionOptions.permissionMode verbatim to the flag. `manual` is deliberately still omitted — it reads as a duplicate of "Default" in the UI, though the two differ (Default passes no flag and inherits the user's configured default; manual forces prompting). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ifacts by arch
Two problems in the macOS build, both found while diagnosing a slow install.
build:mac ended with `codesign --sign - --force --deep dist/mac-arm64/...`.
electron-builder had already signed the bundle with the Developer ID, and this
replaced that with an ad-hoc signature — `codesign -dv` on the result reported
`Signature=adhoc`, `TeamIdentifier=not set`, and `spctl` rejected it. Because
`--sign -` re-signs without entitlements, it also dropped the entitlements the
mac config asks for alongside hardenedRuntime. Shipped DMGs escaped only
because electron-builder writes them before this step ran.
The trailing `; true` applied to the whole && chain, so a failing
electron-builder still exited 0 — build failures could not be reported.
build:mac:arm64 was worse: it also skipped bundle:codemirror (so it could
package a stale or missing codemirror-bundle.js, which is gitignored and
therefore absent in a clean checkout) and hid electron-builder's stderr behind
2>/dev/null.
Artifact naming left the Intel DMG as `Switchboard-<version>.dmg` while the
arm64 one was suffixed, so the x64 build read as the default — easy to install
by mistake on Apple Silicon, where it runs under Rosetta 2 and is several times
slower. An explicit artifactName gives both an arch suffix; verified by build
that ${arch} does expand to `x64` rather than being omitted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…g missing (#60) The only full disk scan (populateCacheViaWorker) runs solely when the cache is completely empty, so a project folder that changed while the app was closed — or that predates the build which first indexed it — was never picked up, and its sessions silently vanished from the sidebar. Replaces the unused populateCacheFromFilesystem with reconcileCacheFromFilesystem, which re-indexes only folders that are new or whose newest .jsonl is newer than the recorded indexMtimeMs, and calls it from the get-projects handler. The gate is stat-only, so it is cheap when nothing changed. Co-authored-by: ymajoros <yannick@valuya.be>
…ading JSONLs (#65) scanSchedules ran every 60s and, for each project folder, read the first 4KB of every JSONL just to recover the folder's cwd. It now prefers the cached folder→projectPath map from cache_meta (one DB query) and only falls back to reading JSONL heads for folders genuinely missing from the cache. The db require is lazy so importing schedule-runner.js never forces the native better-sqlite3 binding to load. Co-authored-by: JeanBaptisteRenard <jean-baptiste.renard@skaleet.com>
…ll strings (#32) Session and scheduled-task commands were assembled by concatenating user-controlled values into a shell string. Values now go into an argv array quoted at the boundary by quoteArgvForShell(), so a crafted worktree name, permission mode, add-dir, or schedule frontmatter field can no longer inject either a shell command or an extra CLI flag (e.g. --dangerously-skip-permissions). Also: - claude-auth.js reads the Keychain via execFileSync, so $USER is never interpolated into a command string. - Drops the --append-system-prompt "$(cat '/tmp/...')" trick, which was a live command-substitution site and left the system prompt in a world-readable temp file that was never cleaned up. - preLaunchCmd stays raw shell by design (e.g. 'aws-vault exec profile --'); only newlines are rejected. Adds test/schedule-injection.test.js covering bash and PowerShell quoting. Co-authored-by: joeytwiddle <joeytwiddle@gmail.com>
) Replacing the binary while Switchboard is running (e.g. overwriting the AppImage) makes the OS spawn a second instance, which initialises a second process and leaves the first one's node-pty sessions orphaned or killed. Takes the single-instance lock at startup: a second launch quits immediately, and the running instance restores and focuses its window via the second-instance handler. Note the app.whenReady() body is now nested one level inside the lock's else-branch, so review with 'git diff -w' — the only semantic change is the lock itself. Co-authored-by: JeanBaptisteRenard <jean-baptiste.renard@skaleet.com>
… show up
/usage in Claude Code shows a third bar the Stats tab did not — e.g.
"Current week (Fable) · 8% used" — because transformUsageResponse probed a
fixed list of top-level bucket keys that the API has moved past.
On a live account, seven_day_sonnet and seven_day_opus now return null, as do
nine codenamed buckets (tangelo, nimbus_quill, cinder_cove, …). Only five_hour,
seven_day and extra_usage still carry data up there. The per-model numbers live
in `limits`, a self-describing array where each row states its own kind,
percent, resets_at, and — for model-scoped windows — the model's display name:
{"kind":"weekly_scoped","percent":8,"resets_at":"…",
"scope":{"model":{"display_name":"Fable"}}}
mapLimits() renders whatever rows come back instead of asking for keys by name,
so a newly launched model appears without a code change — which is exactly the
failure mode that lost Sonnet and Opus here.
The flat session/weekAll keys stay for existing callers, and are backfilled from
`limits` should those buckets go null too, the same way the model-specific ones
already did.
Also clamps the bar fill: it was Math.max(pct, 1) with no upper bound, so a
bucket over 100% would overflow its track.
Verified against a live account — the tab now renders Current session 1%,
Week (all models) 13%, Week (Fable) 8%, matching /usage exactly.
Not included: the "what's contributing to your limits" breakdown from /usage
(context sizes, MCP servers, skills). That is not in this API — Claude Code
computes it locally from session files, as its own header states.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds two gauges to the status bar: session context usage (read from the tail of the session .jsonl via a new get-session-tokens IPC handler) and 5-hour quota usage (via the existing get-usage handler), with the quota gauge linking to the Stats tab. The windows-2022 runner pin from this branch landed separately as #85. Co-authored-by: Flaykz <flaykz@users.noreply.github.com>
Follow-up to #72. Quota: the gauge showed only the 5-hour window, which is usually the emptiest and resets within the day — it reads "plenty left" while a weekly window is the one actually running out. It now renders a bar per row of the usage API's `limits` array (43ab0d0): a 5-hour session window, a weekly all-models window, and a weekly window per model. Rows are self-describing, so shortQuotaLabel only maps the two fixed kinds and otherwise uses the model's display name — a newly launched model gets a bar with no code change here, which is precisely the failure that had lost Sonnet and Opus. Each bar carries its own tooltip with the full label and reset time; the group still clicks through to Stats. Tracks narrowed 48px → 34px so several fit. Context gauge: removed. Claude Code already renders context usage in its own statusline (/statusline) and knows its real limit, where this had to guess — the guess was a hardcoded 200000, so on a 1M-context model it read ~500% and sat pinned red. Duplicating a native display isn't worth carrying, so this removes refreshCtxGauge, CTX_MAX, fmtTokens, both call sites (setActiveSession and the onCliBusyState handler — left in place they would have thrown ReferenceError on every session switch), the #status-bar-ctx markup and CSS, the get-session-tokens IPC handler, and its preload binding. Also replaces the five individually-enumerated /.idea/ entries #72 added to .gitignore with `.idea/` itself, so the whole JetBrains directory is covered rather than the handful of files that happened to exist at the time. Net -102/+68 while adding windows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
builder-util-runtime < 9.7.0 (GHSA-p2f4-r6v6-j797, CVSS 8.2) stripped credential headers on cross-origin redirects with a case-sensitive key check, so PRIVATE-TOKEN and mixed-case Authorization were never inspected and were forwarded to the redirect destination. electron-updater is a runtime dependency and ships inside the packaged app, so the vulnerable code was shipping. Bumping electron-updater to ^6.8.9 resolves builder-util-runtime to 9.7.0 natively — a version combination its maintainers support. The electron-builder devDependency toolchain keeps 9.5.1 under its own nested node_modules and is excluded from the package. An earlier revision forced the version with an npm overrides block; that was dropped after review, since all five electron-builder toolchain packages pin builder-util-runtime to exactly 9.5.1 and a silent mismatch in electron-updater would break auto-update — leaving users unable to update to the fix. This app publishes via GitHub releases, so the authenticated-GitLab path in the advisory is not exercised; the redirect handler is shared across providers, so the upgrade closes the class regardless. Co-authored-by: anupamme <anupamme@users.noreply.github.com>
ws < 8.20.2 has an uninitialized-memory disclosure and a memory-exhaustion DoS from tiny fragments. ws is a direct production dependency and ships in the packaged app, so unlike the rest of the current audit output this one reaches users. Real-world exposure is narrow: the only consumer is mcp-bridge.js, whose WebSocketServer binds host 127.0.0.1 behind a UUID auth token and an `mcp` subprotocol gate, so reaching it needs a local process already running as the user. Bumping anyway — it is a same-major patch move with no API change. Verified 8.21.1 still satisfies the bridge: server binds to loopback, handleProtocols accepts, and a client handshake with subprotocol "mcp" completes. For the record on the rest of `npm audit`: 17 of the 19 findings are electron-builder's own toolchain (tar, lodash, js-yaml, dmg-builder, …), which runs at package time and is excluded from the app. builder-util-runtime is flagged only on electron-builder's nested 9.5.1 copies — the shipping copy resolves to 9.7.0 via electron-updater 6.8.9 (#86). The remaining shipping one is js-yaml, reachable only through update-feed parsing, i.e. only exploitable by whoever already controls the release feed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The "Switchboard" and "Applications" labels under the DMG icons were unreadable against the dark background. Finder owns that text colour — nothing in the .DS_Store or electron-builder's dmg config can set it — and it draws unselected DMG icon labels in near-black regardless of system appearance; disk image windows don't follow Dark Mode. Black glyphs on a near-black backdrop measured about 2.5:1. Since the text can't be lightened, the background is. A light gradient puts the labels at 18.4:1 and 17.7:1, and needs no per-label plates or bands to rescue them — earlier attempts at both read as stickers pasted over the art. The dot grid, arrow and "Drag to install" caption are recoloured to suit; only the installer window changes, not the app's own dark theme. Also collapses the 1x and 2x renders into a single draw(ctx). They were two copies of the same ~50 lines, which is how they drift the moment anyone edits one. Icon coordinates are named constants with a comment tying them to build.dmg.contents. Verified the generator is deterministic — regenerating produces byte-identical PNGs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Escape/Enter removed the listener only on their own paths; closing via the overlay click or close button leaked it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…meta line Opening a session appends untimestamped bookkeeping records (last-prompt, mode, ai-title) that bump the JSONL mtime, so idle sessions showed "just now" and jumped to the top of the sidebar. - created/modified now come from the first/last message timestamps in the JSONL; new fileMtime column takes over as the re-index invalidation key (db migration v4 clears the cache for a one-time re-index) - drop the renderer's lastActivityTime override: it stamped "now" on any PTY output, so the TUI redraw alone kept open sessions at "just now"; labels now track real messages via the file watcher - compact session items: full session-id line replaced by the first UUID segment right-aligned on the time/msgs line (full id in tooltip) - SWITCHBOARD_DATA_DIR isolates a dev instance (own DB + Electron userData/single-instance lock); new npm run electron-dev target Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s bar The "5h" / "Week" labels and percentages were 10px while neighboring text (e.g. the updater status) inherited 11px; drop the overrides so they inherit the status bar size. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lent-close Keep terminal mounted with an exit banner when a session dies
The relaunch/dismiss instructions were wordier than needed — the exit marker itself is the signal that matters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brings in the 29 upstream commits our fork was behind, including three security fixes: - cd8495d bumps ws to ^8.21.1 (GHSA-58qx-3vcg-4xpx, GHSA-96hv-2xvq-fx4p) - b2489df bumps electron-updater to ^6.8.9 (CVE-2026-54673) - 31c3029 builds claude/scheduler commands as argv, not shell strings Plus usage/quota status-bar gauges, the requestSingleInstanceLock PTY-loss fix, scheduler projectPath caching, Wayland clipboard via OSC 52, and upstream's own `auto` permission mode (a7698f4). 17 files conflicted. Notable resolutions: - public/utils.js, dialogs.js, settings-panel.js applied a7698f4 cleanly (our equivalent work is on the unmerged feat/permission-mode-auto branch), so PERMISSION_MODES is upstream's shape and wording. - main.js: took our side everywhere except `--worktree`, where upstream's argv conversion is required — our `claudeCmd +=` predates the argv refactor and would now be a TDZ error. - db.js: unioned our subagent columns with upstream's fileMtime, and renumbered upstream's fileMtime migration v4 -> v7 because migrations are index-addressed (db_version == migrations.length). - session-cache.js / read-session-file.js: adopted upstream's split of `modified` (last message timestamp, display) from `fileMtime` (re-index invalidation key). The header-only refresh path keeps writing the file mtime to `modified` since it only reads the first 256 KB and cannot see the last message; the cold-start full read corrects it. - public/app.js: restored lastActivityTime/activityNoiseRe/trackActivity, which upstream deleted but terminal-manager.js still calls — the clean merge would have thrown a ReferenceError on every terminal chunk. - eslint.config.js: registered upstream's new cross-file renderer globals (shortProjectPath, PERMISSION_MODES) and terminal-manager.js's CJS footer; upstream ships no ESLint config so it never had to. Tests 365/365. Lint 0 errors, 252 warnings (248 on origin/main + 4 of the pre-existing cross-file-global pattern).
…ion mode Scheduled runs hardcoded `acceptEdits` as their permission mode. Align the default with SETTING_DEFAULTS.permissionMode in main.js: `auto` has Claude classify each action, allowing routine work and stopping for risky ones, which suits an unattended headless run better than blanket edit approval. This is only the fallback. A schedule whose frontmatter sets `cli: permission-mode: <x>` keeps <x> verbatim — including an explicit `acceptEdits`, so schedules that spelled the old default out loud are not silently re-pointed at auto. resolvePermissionMode() makes the absent-vs-configured test explicit instead of riding on `||` truthiness; a blank value (`permission-mode:` with nothing after it, which the frontmatter parser yields as '') counts as unconfigured, since claude rejects an empty --permission-mode. Also updates the task-creator prompt template in schedule-ipc.js, which otherwise keeps generating tasks that pin acceptEdits explicitly. 7 new tests in test/schedule-permission-mode.test.js cover: unconfigured -> auto (with and without a cli block), explicit modes preserved, explicit acceptEdits preserved, blank -> auto, the absent/configured split through the real frontmatter parser, and the template no longer emitting acceptEdits.
This was referenced Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges the 29 upstream commits the fork was behind (
upstream/main, through v0.0.31 ande39f251).origin/mainwas 143 ahead / 29 behind, so this is a merge, not a rebase.Security fixes pulled in
cd8495d—wsto^8.21.1(GHSA-58qx-3vcg-4xpx, GHSA-96hv-2xvq-fx4p)b2489df—electron-updaterto^6.8.9(CVE-2026-54673)31c3029— claude/scheduler commands built as argv instead of shell stringsAlso: usage/quota status-bar gauges,
requestSingleInstanceLockPTY-loss fix, schedulerprojectPathresolution fromcache_meta, Wayland clipboard via OSC 52, and upstream's ownautopermission mode (a7698f4).Conflicts and how they were resolved
17 files conflicted (45 hunks). The ones worth reviewing:
public/utils.js,dialogs.js,settings-panel.jsa7698f4applied cleanly because our equivalent work lives on the still-unmergedfeat/permission-mode-auto.PERMISSION_MODESis now upstream's shape and wording, including its deliberate omission ofmanual. Verified against the CLI 2.1.220 binary, which acceptsdefault/auto/acceptEdits/plan/dontAsk/bypassPermissions.main.js--worktree, where upstream'sclaudeArgs.push()is required: ourclaudeCmd += ' --worktree'predates the argv refactor and would now referenceclaudeCmdbefore itsletdeclaration (TDZ error).db.jsfileMtime. Upstream'sfileMtimemigration is renumbered v4 to v7 because migrations are index-addressed (db_version == migrations.length) and our v4-v6 already shipped.session-cache.js,read-session-file.jsmodified(last message timestamp, display) fromfileMtime(re-index invalidation key), including switching the skip check tofileMtime. Without that switch every dirty file would be re-read on every watcher flush.public/app.jslastActivityTime/activityNoiseRe/trackActivity. Upstream deleted all three; ourterminal-manager.jsstill callstrackActivity()on every data chunk, so the clean merge would have thrown aReferenceErroron the terminal hot path.public/sidebar.jsshortProjectPath()helper but kept ourmissingIconand, importantly, ourescapeHtml()— upstream's variant interpolates the raw path intoinnerHTML.eslint.config.jsshortProjectPath,PERMISSION_MODES) andterminal-manager.js's CJS footer. Upstream ships no ESLint config, so it never had to.Add/add conflicts in
test/reconcile-cache.test.jsandtest/schedule-injection.test.jsresolved to ours — both are refactored supersets of upstream's versions (16 vs 10 tests for schedule-injection) with no unique upstream assertions.One thing that needs a call
Our header-only refresh path (
readSessionDisplayHeader) reads only the first 256 KB of a transcript, so it cannot know the last message timestamp that upstream'smodifiednow means. It therefore keeps writing the file mtime tomodified, so live sessions still sort to the top; the cold-start full read corrects it. Consequence: a resumed-but-idle session can still read "just now" until the next cold start — the bug upstream's7d619a6set out to fix, still present on the incremental path only. Fixing it properly needs a tail read in the header path, which is new work, not a merge resolution.Relatedly,
lastActivityTimeis now write-only: the merged sidebar and grid sort and label fromsession.modified(upstream's shape) instead. It is kept only becauseterminal-manager.jscallstrackActivity(). Cleaning it up, or re-wiring activity-based ordering on top of it, is a follow-up.Also in this branch
feat(scheduler): headless scheduled tasks now default toautoinstead of a hardcodedacceptEdits, matchingSETTING_DEFAULTS.permissionMode. Only the fallback changed — a schedule with an explicitcli: permission-mode:keeps it verbatim, including an explicitacceptEdits. 7 new tests intest/schedule-permission-mode.test.js.Verification
npx node --test— 372/372 pass (365 after the merge, +7 from the scheduler change)npx eslint .— 0 errors, 252 warnings (248 measured onorigin/mainbefore the merge, +4 from declaringshortProjectPath/PERMISSION_MODESas cross-file globals, the same benign self-declaration pattern the config already produces throughout)Three tests needed updating because they encoded the old
modified-as-mtime semantics upstream deliberately changed; each now asserts the new split explicitly rather than being loosened.task build).