Repository navigation
feat(windows): choose ClaudeRipple's CPU priority, Normal by default - #117
Merged
Merged
Conversation
…ompts reach every provider unaltered Whether a request was an Auto mode classifier stage, a web search, a web fetch or a turn was worked out on several lines of proxy.ts and never reached the adapters, so every translated provider added to the classifier's security prompt: ChatGPT put an identity line in front of it and the image-generation hint after it, and the OpenAI-compatible, Google, Grok and Anthropic-compatible paths added the identity line and the addendum. ARCHITECTURE §2 promises that prompt stays intact. requestKind() now decides the kind once, and the route carries it with the model the client asked for. decide() returns the route or the reason to refuse it in one pass, replacing unroutableReason, which re-implemented resolve's rules to explain them; resolve() stays for callers without a path. promptAdditions() is the one rule for what a routed system prompt may gain, and a classifier request gains nothing. Failover names `asked` instead of splitting the log tag. Behaviour is otherwise unchanged. The ChatGPT path now trims the addendum as the others did. A prefix rule naming an ingress-only provider still lets a marker to a model nothing can take be refused by name; a test pins that case, which folding the two functions together had briefly lost. Implementation by GPT Daybreak Blue to a fixed design; reviewed here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Five deepening candidates with a before/after flow graph each, a top recommendation, and what should be left alone. The router change before this commit is its candidate 1; candidate 3 comes next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings PBJ-2#91's review fixes and current upstream main. The two conflicts kept both sides: upstream's conversation key on the request record and the SSE parser tests, beside this branch's request kind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the comment on the first-stage bound from PBJ-2#91. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings current upstream main through PBJ-2#91: provider-qualified model ids and the fix that keeps a Claude request with a delegation marker passing through (PBJ-2#94). Upstream made that fix in resolve() and unroutableReason() side by side. Here both live in decide(): the qualified-id branches are placed as upstream placed them, and every way of finding no route ends in one noRoute() that applies upstream's refusal rules in their order. Checked against PBJ-2#91's resolve() and unroutableReason() over 41,664 combinations of config, model and body (16,124 routed, 10,545 passed through, the rest refused with all five messages): no difference. The upstream tests that called unroutableReason() now read decide(...).refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 19 tests that fail on main's Windows job, and why:
- Same folder, different spelling. The runner's TEMP is the 8.3 alias
C:\Users\RUNNER~1\...; the product records the native long form, and on
Windows the lowercased installation home. desktop-legacy, direct-ownership,
the Codex block and the git creation tests now build their fixture roots with
fs.realpathSync.native and compare through the product's own normalizeHome.
The desktop-legacy fixture also sets USERPROFILE, which os.homedir() reads on
Windows, so its default-home case stops seeing the runner's real home.
- A file name Windows forbids. The launcher simulations for darwin and linux
made a home named with a double quote; on a Windows host it keeps the other
hostile characters (Korean, an apostrophe, spaces) and drops only the quote.
The three that need symlinks skip where the account cannot make one.
- The browse test expected the listing without the drive entries the server puts
first on Windows (folders.test.ts already pins those); it now expects them.
Product fixes the failures exposed:
- images.ts: the session temp scope and the allowed-root checks mixed TEMP's
8.3 spelling with the long form Windows returns, and compared paths case by
case, so a legitimate pasted image came back "blocked". ACL and identity
checks now run on the native long form, Windows paths compare without case,
and TEMP itself is still refused when it is a junction. The ACL, junction,
symlink and identity re-checks are unchanged.
- tray.ts: on Windows renameSync replaces an existing file, so a backup path
already taken let the swap replace the working runtime. The swap now refuses
an occupied backup path and leaves both copies whole.
- launcher.ts: removing the tray launcher uses unlinkSync with ENOENT ignored;
fs.rmSync({ force: true }) on that hostile file name ended the Node 24.1
process with 0xC0000409 on Windows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
targets.ts answers the questions the router asked in a dozen places:
- isTargetProvider / ingressOnly: whether a provider can take routed
requests. It was spelled two ways ("anthropic without accountPool" and
"accountPool with claude-code auth") across routing, validate, the proxy,
agent files, model scores and the admin status. One spelling now, the
stricter one, which a valid config never tells apart.
- declaredBy / qualifiedModel / rawDeclaredBy / soleOwner, moved from
routing.ts (still re-exported there).
- effortLevels, moved out of the HTTP module the proxy imported it from
(still re-exported from admin.ts).
- runsServerTools / answersWebSearch: the proxy's canRunServerTools and the
admin's hand-copied mirror of it become one implementation.
- targetWarnings: a primary route or a direct rule naming an ingress-only
provider was accepted by validate and then dropped by routing without a word.
It is still accepted (refusing it would break configs that load today), but
the config save answers with a warning per rule and the router logs it when
it reads the config.
The dashboard asks the same question of providers not saved yet, so it keeps
one copy, isTargetProvider in app.js, used where it had three inline checks;
ui-targets.test.ts holds that copy to the router's for every provider shape.
One difference that comes with the single spelling: a config with accountPool
true and an auth other than claude-code is refused by validate but adopted at
startup. It is now ingress-only everywhere; before, routing treated it as a
target and the proxy then answered 400.
decide() still matches the previous rules over the 41,664-case comparison.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The replacement race in images.test.ts swapped the folder only when the reader opened one exact spelling of the path. The reader now opens the native long form, so on a runner whose TEMP is an 8.3 alias the swap never happened and the file read as "ready". The swap now triggers on either spelling, and on Windows it uses a junction, which needs no symlink privilege, so the test also runs on accounts without it (it passes here: the swap is blocked). - The darwin/linux launcher simulation checked that a symlinked home is the same installation. normalizeHome resolves links for POSIX homes only; on a Windows host the simulated homes are Windows paths, compared as text, so that one step runs off Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows session temp check required each folder's owner to be the current user. Folders made by an elevated administrator token belong to the Administrators group instead, so on such a machine (GitHub's Windows runners among them) every pasted image and scratchpad file was blocked from the phone. The owner may now be the user, SYSTEM or Administrators: the principals the same check already trusts in its access rules. A folder another ordinary user owns, or one writable by anyone else, is still refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two conflicts, both kept from each side: - admin.ts: the config save passes the agent dir to the model-slot sync, as main now does, and still appends the target warnings. - proxy.ts: main's pool-affinity capture and credential logging stay; the web-search query is still read from the request kind decided once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test added in 3f908e7 moved HOME to a temporary folder, but on Windows os.homedir() reads USERPROFILE, so the reader looked in the real home and the test failed there. It now sets both and restores each, unset ones included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three conflicts, each kept from both sides: - agents.ts: main moved the worker loop into agentTargets(); the loop there now asks isTargetProvider, as this branch's copy did. - providers/google: handle() takes main's credential handoff and this branch's request kind, in the order the OpenAI adapter takes them. - proxy.ts: the route still comes from decide() once, and main's worker ladder fallbacks then apply to it. canRunServerTools stays removed (targets.ts answers it); main's new ladder and model-health helpers are kept. The Google call passes both the credential and the kind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The legacy-evidence test was skipped wherever file symlinks cannot be made, though only its linked-home check makes one, and that check was already limited to POSIX hosts. The linked-home check is now a test of its own, skipped on Windows, and the legacy and unknown-file assertions run on every account. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dle() The classifier tests for the OpenAI-compatible and Google translations called promptAdditions() directly, so they would not notice the request kind being lost on its way into the adapter. The OpenAI (both wires), Google and Grok adapter tests now pass the kind from requestKind() into handle(), as the ChatGPT adapter test already did, and send a classifier request and an ordinary turn through an adapter with an addendum: the classifier's text arrives exactly as sent, and the turn still gets the identity line and the addendum. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No conflicts. Main now holds PBJ-2#91, so the PR's diff against main is the refactor and the Windows fixes alone. Main's new code repeats none of the target, server-tool or prompt rules this branch moved into targets.ts and identity.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's harness (495df86) now runs the page's own renderAutoClassifier, which on this branch asks isTargetProvider whether a Claude provider is an account pool. The harness slices that function from app.js too, so the redraw test runs the page's code instead of failing on a missing name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A scheduled task registered without a priority runs at Task Scheduler's default 7, BELOW_NORMAL, and the launcher's children inherit it. On 2026-10-29, with cargo and rustc holding every core at Normal, the router took 1-3 s to answer /api/status and refused new connections: an update's probe failed with ECONNREFUSED on a listening port, `install` skipped the version restart because /api/status did not answer in 10 s, and `restart` called the router "another ClaudeRipple installation" because the 5 s listener lookup timed out. - The task is registered with -Priority 4 (Normal). The definition read includes the priority, so an existing task is registered again by the next install; where Register-ScheduledTask is refused, a task that differs only in its priority is kept. - The launcher raises itself from BelowNormal or Idle to Normal before it starts the router, so the router gets Normal under an old registration too. - The router and the phone host raise themselves to Normal at start on Windows and log it. - The listener lookup behind the restart's ownership check waits 15 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fix before this one only raised the router to Normal. This makes the level
a setting with the four Task Manager levels: below-normal (what Task Scheduler
gave the router before), normal (the default), above-normal and high.
- Config.processPriority, validated to those four; absent means normal.
- process-priority.ts applies it on Windows and reads back what is in effect
(idle and realtime included, when another program changed it). The router
applies it after its logger starts and on every config change; the phone
host applies it when it starts. Other platforms report supported: false.
- GET /api/status carries processPriority { supported, setting, current,
error? }.
- The dashboard's About view has a CPU priority menu under Automatic updates.
High asks before it is saved and keeps a warning on the row while it is on;
a level Windows reports otherwise is named. Strings in all six languages,
with each language's Task Manager names.
- docs/ARCHITECTURE.md: the failure row and a section on the setting.
The Task Scheduler -Priority 4 registration and the launcher's raise stay: they
give every start a Normal baseline before the router reads its config.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"CPU를 계속 쓰는 문제가 생기면" brings writing to mind before using. The High warning and its confirm now say "리소스를 과도하게 쓰면". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This PR builds on PBJ-2#98, which fixes the Windows tests that fail on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deferred cleaner's PowerShell did not write "running" within 5 s on a Windows CI runner (the run of PBJ-2#116, 2026-10-29), and uninstall --purge reported that cleanup could not start. A PowerShell start took over 5 s on a loaded desktop the same day. The cleaner now has 20 s to be launched and 15 s to answer; a working one still returns as soon as it answers. The test's child gets 60 s so that the longer waits fit inside it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rt wait Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deferred cleaner writes its receipt and then exits; until it has, Windows keeps its redirected logs open. Removing the folder at once failed with EPERM in the self-purge test's after hook on a CI runner (2026-10-29), although every assertion had passed. Both cleaner tests now remove it through one helper that retries for up to 5 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p retry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The snapshot test makes directOn's backup fail by taking the backup's name
first. The name carries the time to the second, and on a Windows CI runner
the second turned over between the test's name and directOn's, so nothing
failed ("Missing expected exception", 2026-10-29). The test now takes this
second's name and the next two.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a direct test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Repeating the purge tests 20 times on 2026-10-29 failed in three ways, each also reachable by `uninstall --purge` itself: - The CLI read the receipt while the cleaner's Set-Content was writing it, got EBUSY, and reported a cleaner that never started. A busy or empty receipt is now read again on the next round. - After Wait-Process returned, Get-Process by id still listed the exited Node process, and the receipt said "uninstall process has not exited". The cleaner now holds that process's handle and waits on HasExited. - Set-Content could not write the final receipt while a reader had it open, so a purge that had removed the folder recorded itself as failed. The receipt is now written with WriteAllText, which shares the file with readers, and a refused write is tried again. The tests read a receipt that is mid-write as not yet there, and remove the cleaner's folder with their own retry: rmSync's maxRetries does not cover the EPERM of its still-open logs on Node 24. 30 runs in a row passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ver tests On PBJ-2#98's Windows run at f5b9cbd, two permission tests hit their 4.5 s limit and a socket retry test ran out of waitFor's 2 s while the host was still answering; the same tests had passed on the runs before. The limits are for a stalled test, and a passing one never waits them out, so each file now uses TEST_TIMEOUT (15 s) and WAIT_LIMIT (10 s). 211 tests pass locally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Windows CI Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…home The Windows task lock lives under os.homedir(), which reads USERPROFILE on Windows. ownership.test.ts and service-races.test.ts set HOME only, so both took the real home's lock for the default task name, in parallel: on PBJ-2#117's Windows run, service-races found ownership's lock before its owner file was written and failed with "서비스 작업 잠금의 소유자를 확인할 수 없습니다" instead of the race it tests (2026-10-29). Both now point USERPROFILE at their temp folder too, as desktop-legacy.test.ts does; neither touches ~/.clauderipple/service-locks any more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Builds on #98. #98 is merged into this branch because it fixes the Windows tests that fail on main, so
test (windows-latest)can pass here. Merge #98 first; until then this diff also shows its commits.On Windows the router and the phone host ran below Normal priority, and a busy machine starved them. This PR runs them at Normal by default and adds a setting to choose the level.
What was measured (2026-10-29, Windows 11)
ClaudeRippleRouterandClaudeRippleHostare registered with Task Scheduler priority 7.New-ScheduledTaskSettingsSetwithout-Prioritygives 7, which runs the action atBELOW_NORMAL_PRIORITY_CLASS. conhost, the PowerShell launcher andnode …\router\src\index.jswere all BelowNormal. CreateProcess gives a child its parent's class when the parent is Idle or BelowNormal./api/status./api/statusgave no answer withinrunningVersion's 10 s, soinstallskipped the version restart. The end-to-end probe then gotconnect ECONNREFUSED 127.0.0.1:8790from a router that was listening: its accept queue was full.clauderipple restartreported! not restarted: the scheduled task belongs to another ClaudeRipple installation. The PowerShell listener lookup inrouter-state.ts(5 s timeout) had timed out, and its null answer counts as "other".Start-Processwas BelowNormal. Once the PowerShell raised itself to Normal, its next child was Normal.Changes
A Normal baseline (first commit)
schtasks.tsregisters the task with-Priority 4(Normal).readTaskDefinitionreadsSettings.Priority, andtaskDefinitionMatchesrequires 4, so the nextinstallregisters an existing task again.Register-ScheduledTaskis refused (E_ACCESSDENIED), the headless and pre-headless fallbacks keep a task that differs only in its priority. Nothing else in that fallback is loosened.installrewrites the launcher, so existing installs get this with their next update. The phone host's task uses the sameinstallAgentandrenderLauncher.quiet()inrouter-state.tswaits 15 s instead of 5 s. What a null answer means is unchanged.The setting (second commit)
Config.processPriorityis one ofbelow-normal,normal,above-normalorhigh, the Task Manager levels. It is validated, and an absent value meansnormal.below-normalis what the router ran at before.process-priority.tsapplies it on Windows and reads back what is in effect. That can also beidleorrealtimewhen another program changed it. A level already in effect is left alone and logs nothing; a change logs one line.supported: false, because raising a priority there needs privileges.GET /api/statuscarriesprocessPriority: { supported, setting, current, error? }.supported:docs/ARCHITECTURE.mdgets a row in §5 for this failure and a section on the setting.Tests
process-priority.test.ts:setPriorityis reported in the state and not thrown.config.test.ts: the four levels pass validation;"realtime","High"and1fail with the exact message.admin.test.ts:/api/statuscarries the field.schtasks-definition.test.ts:-Priority 4.while ($true), keeps its BOM and CRLF, and never names AboveNormal, High or RealTime.ui-priority.test.ts(new) runs the page's ownpriorityRowandsavePriority:supported.{level}once.npm run typecheckandcheck:modpass. The router process-priority, config, admin and ui-* tests, the CLI schtasks-definition, ownership, recovery and supervisor-start tests, and the remote platform and local-control tests give 349 tests: 347 passed, 2 skipped, 0 failed.launcher.test.tspasses with refactor(router): a request's kind and a provider's capabilities are each answered in one place, and the Windows suite is fixed #98 merged in.Not done: registering the task again on a real machine, restarting the services with this branch installed, reproducing the CPU load against it, or opening the dashboard in a browser. The row was checked through the test harness only.
🤖 Generated with Claude Code