Repository navigation
Add threads.v8Flags to apply V8 flags such as --optimize-for-size to every worker thread - #3138
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces the threads.v8Flags configuration option, allowing V8 flags to be applied to the main thread before any worker threads are started. It includes schema updates, validation logic, design documentation, and comprehensive unit tests. The review feedback suggests using a namespace import for harperLogger in server/threads/v8Flags.ts to align with the codebase's import style and avoid potential runtime issues with default exports.
…orker starts Node refuses flags such as --optimize-for-size in NODE_OPTIONS and worker execArgv rejects V8 options, so a constrained deployment had no way to pass them. threads.v8Flags (a string or list of strings) is applied with v8.setFlagsFromString on the main thread at startHTTPThreads entry and in startWorker, before any Harper worker isolate exists. Entries not starting with "--" fail startup naming the setting. Unset changes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VKZAtTiprHW8Sp1Ff4hGAZ Dispatch-Task: harper-threads-v8flags
…s tests Joi and the JSON schema now require each entry to start with "--", so set_configuration refuses a bad value before writing or replicating it. Entries are trimmed and empty ones ignored. The confirmation is logged at notify level and says the flags were passed to V8, since V8 gives no success signal. The latch is set after submission. Tests cover the startHTTPThreads entry call, bound their child process, and use a single owned root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VKZAtTiprHW8Sp1Ff4hGAZ Dispatch-Task: harper-threads-v8flags
…nset Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VKZAtTiprHW8Sp1Ff4hGAZ Dispatch-Task: harper-threads-v8flags
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VKZAtTiprHW8Sp1Ff4hGAZ Dispatch-Task: harper-threads-v8flags
kriszyp
force-pushed
the
feat/threads-v8-flags
branch
from
October 13, 2026 14:10
3d662eb to
c07514f
Compare
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.
Note that one alternative is to pass in these settings with CLI flags, but that would involve modifying the harper script that is used to start the node process, and this is probably simpler/cleaner.
⊙ Problem
A constrained deployment (1 GB container, 1 CPU,
threads.count: 1) wants V8's--optimize-for-size. Measured on that shape, it cuts idle memory from about 212 MB to about 173 MB. There is nowhere to pass it: Node refuses it inNODE_OPTIONS("not allowed in NODE_OPTIONS"), worker threads'execArgvaccepts no V8 options, and the container entrypoint is theharpershebang script. V8 flags are process-wide and read when an isolate is created, so setting them in the main thread before the first worker starts reaches every worker.💡 Solution
New setting
threads.v8Flags, a string or a list of strings:applyConfiguredV8Flags()reads it once per process, on the main thread. It validates every entry, then passes each tov8.setFlagsFromStringand logs the result atnotifylevel. It runs atstartHTTPThreads()entry and again instartWorker(), which holds the only productionnew Worker. So the flags are set before any Harper worker isolate exists, including underthreads.count: 0, where the first worker may be a job started long after the server is serving. Unset,null, or[]changes nothing. Bun logs a warning and applies nothing.Each entry must start with
--(surrounding whitespace is trimmed, empty entries are dropped). Joi and the JSON schema enforce it, soset_configurationrefuses a bad value before writing or replicating it. A bad value supplied at boot (file edit, env var,HARPER_SET_CONFIG) stops startup with an error namingthreads.v8Flags.Runtime flags skip the CLI's implied flags. Measured on Node 26.2.0 with a worker given Harper's 1 GB-tier limits (
maxOldGenerationSizeMb: 512,maxYoungGenerationSizeMb: 16):--optimize-for-sizegiven…setFlagsFromString)--max-semi-space-size=1The CLI form implies a 1 MB semi-space; the runtime call does not. This probably explains why the in-process measurement under load (206–211 MB) trailed the CLI (about 195 MB).
⚖️ Alternatives
manageThreads.jsmodule load, next toheapSnapshotNearLimit(the seed design). The planning review returnedFraming-Verdict: better-alternative-exists. Adopted its alternative: initialize atstartHTTPThreadsentry and keep thestartWorkerguard. Disqualifier for module load: config is not final then (install and env-var writes come later inbin/run.ts).node --optimize-for-size $(command -v harper) run. Gives exact CLI semantics, including the main isolate. Rejected because the requirement is a portable config key that host-manager sets throughHARPER_SET_CONFIG.execArgvof Harper's own relaunch fork (utility/processManagement/servicesConfig.js, used bylaunch()/restart). Either would get CLI implications for free. But a container runsharper rundirectly, andHARPER_EXIT_ON_RESTARTexits instead of relaunching, so that fork never runs there; a new re-exec changes the process tree and PID-1 behavior.threads.preloadRequirewith a module callingsetFlagsFromString. It runs inside an already-created worker, too late for flags read at isolate creation, and safe mode skips it.🔧 Changes
server/threads/v8Flags.ts(new):applyConfiguredV8Flags()— main thread only; latches after the first successful call (a rolling restart cannot undo process-wide flags); validates every entry before applying any; Bun no-op with a warning.server/threads/socketRouter.ts: import and call atstartHTTPThreads()entry, before any asynchronous startup work.server/threads/manageThreads.js: import and call instartWorker()after the shutdown guard, before any worker resources are allocated.utility/hdbTerms.ts:CONFIG_PARAMS.THREADS_V8FLAGS, which also gives the env-var andset_configurationnames through the derivedCONFIG_PARAM_MAP.validation/configValidator.ts(entry rule,threads.v8Flags),config-root.schema.json: string or string list ornull, each entry---prefixed (blank allowed). The schema'sthreadskeepsadditionalProperties: true, andmain's validator accepts the unknown key, so older versions ignore it.server/DESIGN.md,DESIGN.md: the invariant (applied before the first Harper worker, never at module load), the latch, the boot-input decision, and the implied-flags caveat.✅ Verification
unitTests/server/threads/v8Flags.test.jswithv8Flags-fixtures/harness.cjsandworker.cjs(8 passing). Each case runs in its own child process, because flags are process-wide. The harness loadsmanageThreadsbefore assigning the setting, asbin/run.tsdoes, then starts a real worker that reportsError.stackTraceLimit. Cases: list and string forms; unset,null,[]and blank; a changed value before a second worker (the first value stays); a refused list leaves V8 untouched and starts no worker; whitespace trimming; a non-string entry; andstartHTTPThreads(0)refusing an invalid value.THREADS_V8FLAGSadded tomain'shdbTerms.ts, 5 of 6 original cases fail (stackTraceLimit: 10, V8's default); the unset case passes, as expected.startHTTPThreadscall fromdistmakes its test fail at the 30 s harness deadline, with the child reaped.unitTests/validation/configValidator.test.js: accepted and refused shapes.End to end, live (built
dist, isolated root,threads.count: 1, a probe component returning the worker'sError.stackTraceLimit):HARPER_SET_CONFIGv8Flags: ["--stack-trace-limit=7"]7THREADS_V8FLAGS='["--stack-trace-limit=5"]'5v8Flags: null10, no log lineTHREADS_V8FLAGS=optimize-for-sizethreads.v8Flagsthreads.count: 0with an invalid value'threads.v8Flags' … fails to match the V8 flag starting with -- patternset_configurationthreads_v8Flags: "optimize-for-size"Gates, run locally on the branch:
test:unit:main6850 passing,test:unit:resources4132 passing,test:integration:all2338 passing, 0 failing, 6 cancelled. All 6 cancelled areOllamaBackend against a real Ollama instance; this box's Ollama lacks the default models, unrelated to this change.lint:requiredand prettier are clean.Pre-push review: planning round plus full and delta code rounds (Codex graded, Gemini, Cursor Composer, Harper domain adjudication). The major (a bad value accepted by
set_configurationreplicating to every node) is fixed. Declined: listingv8Flags: nullinstatic/defaultConfig.yaml; the docs are the discovery surface.Docs: HarperFast/documentation#724 — Document threads.v8Flags (options reference,
set_configurationrestart notes, 5.4 release notes).Belongs with Reduce each thread's base JavaScript heap by about 40% in the "Worker startup heap" group.
🤖 Generated by Claude Code (Claude Opus 5.5); posted via @kriszyp.
🤖 Generated with Claude Code
https://claude.ai/code/session_01VKZAtTiprHW8Sp1Ff4hGAZ
Related PRs: none found
Complexity: medium
Origin — the dispatch brief this PR was written from
Add threads.v8Flags: V8 flags applied process-wide before worker threads start
Add a Harper configuration option
threads.v8Flags(a string or an array of strings, e.g. ["--optimize-for-size"]) whose V8 flags are applied in the main thread withv8.setFlagsFromStringbefore any worker thread is created, so every worker isolate (and the main isolate from that point on) runs with them. Follow harper-engineering-guidelines and the DLC, open a draft PR against main with milestone v5.4, and open a companion HarperFast/documentation PR documenting the option.Acceptance
threads.v8Flags(string or string[]) applied via v8.setFlagsFromString in the main thread before any Worker is constructed; no-op under Bun; invalid entries (not starting with--) fail startup naming the setting.Dispatch: task
harper-threads-v8flags· queued by kris-session · ran by claude/opus/high · worker kzyp-xps-1Review-Coverage: authored=claude; ran=gemini,codex; adjudicated=domain; declined=cursor-grok,cursor-composer,cursor-kimi,cursor-muse; rounds=4; full=2 @ 3d662eb
Review-Attention: study ~9m (hot: manageThreads.js, socketRouter.ts +1; decisions: do-less-alternative, runtime-flags-on-live-main-isolate, unrestricted-expert-setting, safe-mode-still-applies-v8flags) @ 3d662eb