Skip to content

chore: sync fork with upstream v0.0.31 (security fixes + auto permission mode) - #88

Merged
JeanBaptisteRenard merged 31 commits into
mainfrom
chore/sync-upstream-v0.0.31
Aug 3, 2026
Merged

chore: sync fork with upstream v0.0.31 (security fixes + auto permission mode)#88
JeanBaptisteRenard merged 31 commits into
mainfrom
chore/sync-upstream-v0.0.31

Conversation

@JeanBaptisteRenard

Copy link
Copy Markdown
Collaborator

Merges the 29 upstream commits the fork was behind (upstream/main, through v0.0.31 and e39f251). origin/main was 143 ahead / 29 behind, so this is a merge, not a rebase.

Security fixes pulled in

Also: usage/quota status-bar gauges, requestSingleInstanceLock PTY-loss fix, scheduler projectPath resolution from cache_meta, Wayland clipboard via OSC 52, and upstream's own auto permission mode (a7698f4).

Conflicts and how they were resolved

17 files conflicted (45 hunks). The ones worth reviewing:

File Resolution
public/utils.js, dialogs.js, settings-panel.js No conflict — a7698f4 applied cleanly because our equivalent work lives on the still-unmerged feat/permission-mode-auto. PERMISSION_MODES is now upstream's shape and wording, including its deliberate omission of manual. Verified against the CLI 2.1.220 binary, which accepts default/auto/acceptEdits/plan/dontAsk/bypassPermissions.
main.js Ours everywhere except --worktree, where upstream's claudeArgs.push() is required: our claudeCmd += ' --worktree' predates the argv refactor and would now reference claudeCmd before its let declaration (TDZ error).
db.js Unioned our subagent columns with upstream's fileMtime. Upstream's fileMtime migration 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.js Adopted upstream's split of modified (last message timestamp, display) from fileMtime (re-index invalidation key), including switching the skip check to fileMtime. Without that switch every dirty file would be re-read on every watcher flush.
public/app.js Restored lastActivityTime / activityNoiseRe / trackActivity. Upstream deleted all three; our terminal-manager.js still calls trackActivity() on every data chunk, so the clean merge would have thrown a ReferenceError on the terminal hot path.
public/sidebar.js Took upstream's shared shortProjectPath() helper but kept our missingIcon and, importantly, our escapeHtml() — upstream's variant interpolates the raw path into innerHTML.
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.

Add/add conflicts in test/reconcile-cache.test.js and test/schedule-injection.test.js resolved 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's modified now means. It therefore keeps writing the file mtime to modified, 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's 7d619a6 set 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, lastActivityTime is now write-only: the merged sidebar and grid sort and label from session.modified (upstream's shape) instead. It is kept only because terminal-manager.js calls trackActivity(). 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 to auto instead of a hardcoded acceptEdits, matching SETTING_DEFAULTS.permissionMode. Only the fallback changed — a schedule with an explicit cli: permission-mode: keeps it verbatim, including an explicit acceptEdits. 7 new tests in test/schedule-permission-mode.test.js.

Verification

  • npx node --test372/372 pass (365 after the merge, +7 from the scheduler change)
  • npx eslint .0 errors, 252 warnings (248 measured on origin/main before the merge, +4 from declaring shortProjectPath/PERMISSION_MODES as 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.

⚠️ Switchboard runs as a built AppImage — nothing here takes effect until a rebuild (task build).

HaydnG and others added 30 commits June 1, 2026 09:36
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.
@JeanBaptisteRenard
JeanBaptisteRenard merged commit 592423e into main Aug 3, 2026
7 checks passed
@JeanBaptisteRenard
JeanBaptisteRenard deleted the chore/sync-upstream-v0.0.31 branch August 3, 2026 10:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.