Repository navigation
Releases: warlockjs/framework
Release list
v5.41.0
Released 2026-10-29.
All 33 packages (@warlock.js/* and create-warlock) move to 5.41.0 together;
keep every family package on the same version. No breaking changes. This release adds
Mantine as a second UI kit for the design system, next to shadcn/ui.
1. Breaking changes
- None.
warlock add design-systemwithout--kitinstalls the shadcn kit exactly as
before.
2. Mantine UI kit (core, create-warlock, web)
- Install:
warlock add design-system --kit mantine(orwarlock add mantine) in an app
with web pages. It does not need Tailwind. - Same contract as the shadcn kit: the same component files and names under
src/web/design-system/(TextInput,EmailInput,PasswordInput,TextareaInput,
SelectInput,MultiSelectInput, the date and time pickers,CheckboxInput,RadioInput,
SwitchInput,FileInput,FieldFrame,SubmitButton), the same props and the same hooks.
App code and generators work with either kit. - Forms stay on Mongez React Form. Every control binds through the shared hooks, and
validation errors show through Mantine'serrorprop.@mantine/formis not installed or
used. - Setup it writes: pinned
@mantine/core,hooks,dates,notifications,modalsand
dayjs;postcss.config.mjswithpostcss-preset-mantine; Mantine's stylesheets in
src/web/app.css; and insrc/web/root.tsxthe colour-scheme script,mantineHtmlProps,
and a provider that mountsMantineProvider, the modals manager and the notifications host.
Existing files are kept, and a root with a custom shape is left alone with the lines to add
printed. - Theme:
src/web/design-system/mantine-theme.tsholds the design tokens you edit
(primary colour, radius, fonts, spacing). - Extras:
notifyandnotifyOnSubmit(toasts that plug intouseSubmitFormand
useSubmitAction),confirm()for confirmation dialogs, andPaginatedTable, which takes
the paginated list response (rowspluspagination). - Kits exclude each other. Asking for one kit while the other is installed, or for both in
one command, fails before anything is installed or written, with the steps to switch.
3. New projects (create-warlock)
--ui=<shadcn|mantine|none>chooses the UI kit of a web app;shadcnstays the default for
--stack=web.--ui=mantinescaffolds without Tailwind.- The interactive stack menu has a "Full-stack web with Mantine" entry, and Customize lists
Mantine among the rendering features. - The scaffold check in CI can create a Mantine or shadcn web project
(npm run typecheck:scaffold -- --ui=mantine).
4. Upgrade notes
- Update the family. Run
npx warlock update. - Read what changed. Run
npx warlock release-notes --from 5.40.1. - Nothing to change for apps on the shadcn kit.
- To switch an app to Mantine, remove the shadcn kit's files and dependencies (the error
message ofwarlock add design-system --kit mantinelists them), then run the command
again. Your forms keep working because the component names and props are the same.
v5.40.1
Released 2026-10-29.
All 33 packages (@warlock.js/* and create-warlock) move to 5.40.1 together;
keep every family package on the same version. No breaking changes. This patch makes
create-warlock --stack=web work again and cleans up the design-system files it writes.
1. Breaking changes
- None.
2. New web projects install their features again (create-warlock, core)
- Since 5.35,
create-warlock --stack=web(or any scaffold withweb,tailwind,shadcn,
design-systemorsitemap) failed in the feature step:warlock addloads
warlock.config.tsfirst, the web starter's config imports@warlock.js/web, and that
package was only installed by the feature step itself. Every feature, even the database, was
reported as failed and the project was left without them. - A web scaffold now lists
@warlock.js/webinpackage.jsonfrom the start, and
warlock addcontinues with a warning when the project config cannot load yet, so
npx warlock add webalso works on a project in that state. - CI now scaffolds and typechecks a web project as well as the API-only one.
3. Design-system files pass the app's lint (create-warlock, core)
- The controls and hooks that
warlock add design-systemwrites intosrc/web/design-system/
failed the scaffolded Oxlint config, sowarlock devreported 13 lint errors in a fresh web
app. They now pass, and a spec keeps it that way.
4. Upgrade notes
- Update the family. Run
npx warlock update. - A web app scaffolded on 5.35–5.40.0 without its features: run
npx warlock add web(then any other features you chose, such astailwind,
design-systemor your database) after updating. - Lint errors in
src/web/design-system/: rerunnpx warlock add design-systemto get the
fixed files, or keep yours if you edited them.
v5.40.0
Released 2026-10-29.
All 33 packages (@warlock.js/* and create-warlock) move to 5.40.0 together;
keep every family package on the same version. No breaking changes. This release makes
warlock check finish on large apps, makes the development server steadier and quieter,
brings Windows up to a tested platform, and adds turn control, context editing, spend
limits and approvals to the AI agent loop.
1. Breaking changes
- None. Two additions can surface in strict code; see the upgrade notes:
FinishReasonhas a new member"tool_stop", andhttp.log: truenow writes one summary
line per request instead of one line per phase.
2. warlock check finishes on large apps (core)
- On a large app the conventions step could run for tens of minutes at full CPU without
printing anything. Two causes are fixed:- The
.clientboundary rule (WL9022) re-read, re-parsed and re-resolved every shared
file for each controller, service, model and route it checked. Import chains are now read
once per run, and files known to reach no.clientmodule are never walked again. - Parsed syntax trees kept their parent links in shared lookup tables. With thousands of
trees alive, memory cleanup stalled for seconds at a time. The links now live on the nodes,
and the import rules reuse one parse per file.
- The
- On an app with about 7,500 source files, the conventions step now takes about 14 seconds.
- Progress lines.
warlock checkprints one line to stderr when each step starts and
ends, and the conventions step reports its milestones. stdout is unchanged, so--json
still prints only the report. - Per-rule time budget. Each convention rule may use 30 seconds over a run. A rule that
goes over is skipped for the remaining files and reported as aWL9026warning, instead
of hanging the command. SetruleBudgetMsin.warlock-check.json;0turns it off.
3. A steadier, quieter development server (core, web, fs)
- Spec and test files are never auto-loaded. Command discovery,
main,routes,
worker,eventsand locale files, and the standalonecascade migrateloader skip
*.spec.*and*.test.*files, so avi.mock()beside a command no longer breaks the CLI. - The first page after a restart waits for the compile. In development the page loader
timeout defaults to 90 seconds and starts only after a reload in progress has settled.
Production keeps 15 seconds; an explicitweb.loaderTimeoutstill wins. - Smaller reloads. Importers that reach a changed file only through
import typeare no
longer reloaded, only the changed files are re-read, and the+N modulescount is per file.
Adding files at boot no longer re-scans every tracked file. - Requests survive reloads. New requests wait while a reload runs; requests already
running get up to 5 seconds to finish. A request held longer than 15 seconds gets a503
withRetry-After: 2.putJsonFileand the typings manifest are written atomically. - Module singletons.
appSingleton(key, create, { dispose, disposeOnReload })keeps one
instance per process across reloads and across the copy that pages load through Vite, and
disposes it on shutdown. A secondsupervisor()for the same registry and owner in one
process returns the running supervisor instead of failing with a held lock. - Less log noise.
- With
web.moduleBoundary: "warn", each boundary violation prints once per dev session,
then a count of repeats. - With
http.log: true, each request writes oneinfoline
(GET /orders 200 12ms [request id]); the per-phase lines move todebug.
http.logPhases: "info"restores the old output. - AI tool schema lint takes
lint: { ignore: [codes] }per tool, in
ai.config({ toolLint })and onai.mcp(server, { lint }), and prints each finding once
per process.
- With
- App ready hook.
Application.onceBooted()/whenBooted()runs after every connector
has started; the docs now say when it fires.
4. Windows (process, core, fs, ai, web)
- Process supervisor.
- The guardian answers the resume request before it starts the program, so a slow start
can no longer time out the ownership check. observationTimeoutMsis a supervisor option: 2 seconds on Linux, 15 on Windows and
macOS. The timeout error names the option.pollIntervalMsdefaults per platform: 100 ms on Linux, 500 ms on macOS, 1.5 s on
Windows.- On Windows one long-lived helper answers every process lookup in batches, instead of two
PowerShell processes per lookup, and the launch gate polls the guardian pipe directly.
- The guardian answers the resume request before it starts the program, so a slow start
- Fixed on Windows: image variants answered 500 when the cache path went over 260
characters;warlock typecheck,warlock check --changedandwarlock inspectcould not
match the compiler's paths; the development model registry keyed a deleted model
differently; end-to-end browsers ignored their profile;warlock releaselinked
node_moduleswith a directory symlink, which needs administrator rights. ai.snapshot.sqlitestores haveclose(), so their database file can be removed.- The framework's own test suites for
fs,process,core,webandainow run on
Windows in CI.
5. AI agent loop (ai, ai-anthropic)
- End a turn after a tool.
stopAfterTools: [...],stopWhen(ctx)on the agent, or
endsTurnon a tool ends the run with no further model call; the finish reason is
"tool_stop". - Context editing.
contextEditing: { clearToolResults }replaces old tool results with
a placeholder before each trip, for every provider. Anthropic models use the provider's
native clearing. It runs beforecompaction, which still summarises. - Prices and spend.
ai.pricing({ tables })builds one overridable price list from the
provider tables.budget({ onSpend })reports spend after every call, and
scoped: { window: "all" }caps a session or a lifetime. - Approvals.
ai.human.approval()takes per-tool modes (allow,deny,ask) and
remember, so an approval can cover the rest of the session for that tool or those
arguments.
6. AI providers and tools
- xAI: calls that use native web or X search go through the Responses API; other calls
stay on Chat Completions (api: "chat" | "responses"to choose). - OpenAI: a call with
web_searchis sent to the Responses API automatically; a model
that cannot search fails with a clear error before any request. - Google:
googleSearchBudget(n)stops sending search oncenqueries were used. The
Gemini API has no per-call cap, so one running call can still go over. - Model listing: every first-party adapter implements
models(), a live listing in one
shape (ModelInfo). - Workspace patches:
@warlock.js/ai-workspaceadds an atomicapply_patchtool for
unified diffs (modify, create, delete, rename), confined to the workspace.
7. Other fixes
- Manual
beginTransaction()covers the caller's queries (cascade). After
await beginTransaction(), the queries the caller runs use the transaction until commit or
rollback. Before, they ran outside it on MongoDB and on other pool connections on
PostgreSQL.transaction(fn)is unchanged. - New API-only projects typecheck (
create-warlock). A project created without web pages
no longer receivessrc/config/web.ts, which imported a package it does not install. - New projects tell agents how to upgrade (
create-warlock). The generatedAGENTS.mdhas an
"Upgrading Warlock.js" section: readnpx warlock release-notes --from <current>(bundled with
core, so it works offline), runnpx warlock update, follow the upgrade notes, then
npx warlock check. It also explains that each package's skills arrive as one router skill.
8. Upgrade notes
- Update the family. Run
npx warlock update. - Read what changed. Run
npx warlock release-notes --from 5.39.2. - Run
npx warlock check. It should now finish on a large app. AWL9026warning names
a rule that ran out of time; raiseruleBudgetMsif your app needs more. - Exhaustive
FinishReasonswitches. Add a"tool_stop"case where you switch over
every finish reason. - Request logs. If you rely on per-phase request lines at
info, set
http.logPhases: "info". - Replace app workarounds. Module-level singletons kept on
globalThiscan use
appSingleton(). A pinned longer ownership timeout or poll interval on Windows can usually
go. Specs moved away from command folders can come back. - Review manual transactions. Code that calls
beginTransaction()now runs its later
queries inside the transaction, as the API always promised. Make sure each one commits or
rolls back.
v5.39.2
Released 2026-10-27.
All 33 packages (@warlock.js/* and create-warlock) move to 5.39.2 together;
keep every family package on the same version. No breaking changes. This release lets
model.set() take an object and adds a check that catches a recurring deep-merge mistake.
1. Breaking changes
- None. 5.39.2 has no breaking changes.
merge()behaves exactly as before, and
set(key, value)is unchanged.
2. model.set() accepts an object (cascade)
model.set({ ... })sets several fields in one call. Each entry behaves exactly like
set(key, value)for that key, with the same casting, dirty tracking and whole-field update.- One rule:
setreplaces, a dot path targets one leaf, andmergedeep-merges. A
top-level key replaces the whole field (objects and arrays are stored whole, keys the new
value omits are removed when you save). A dot-path key such as"settings.theme"writes only
that leaf and keeps its siblings. - It applies the same identity protection as
merge(): on a persisted model,id,_id
and the primary key in the object are ignored, somodel.set(req.body)cannot retarget the
save. Cascade has no guarded or fillable list, so choose which fields you pass. - Keys that overlap in one call (
settingswithsettings.theme, ora.bwitha.b.c) throw
an error that names both keys, before anything is written. - In a typed model each key is a schema column, a system column or a dot path, and each value
is checked against its column type. The method returns the model. merge()'s editor documentation now says that nested objects are deep-merged and that
removed keys are kept, and points toset({ ... })andset(key, value).
// settings is stored as { theme: "dark", filters: { status: "open" } }
post.merge({ settings: { theme: "light" } }); // filters is kept
post.set({ settings: { theme: "light" } }); // filters is removed
post.set({ "settings.theme": "light" }); // only theme changes, filters is kept
await post.save();3. warlock check flags merge() on object columns (core)
WL9025(warning) reports amodel.merge({ ... })call whose object literal includes
a column that the model'sstatic schemadeclares as an object, record or array-of-objects
validator. The message names the column, explains that removed keys are kept, and suggests
set({ column: ... })orset("column", value). It has no automatic fix, because the two
methods behave differently.- Scalar columns,
set(), spreads, variables, andmerge()on objects that are not
Cascade models are not reported. Test files are skipped.
4. Work scheduled inside a transaction no longer reuses its session (cascade)
- A
setTimeout, queue or fire-and-forget promise armed insidetransaction()used to keep
the transaction's session. After the transaction ended, a Cascade query in that work threw
MongoExpiredSessionErroron MongoDB, and on PostgreSQL ran on a pool client that had
already been released. - Now, once a transaction commits, rolls back or fails, that work runs with no session,
outside any transaction: its queries are ordinary queries,afterCommit()runs on the next
microtask, and atransaction()it starts opens a fresh transaction. The work is not rolled
back with the transaction. For work that must run after the COMMIT, useafterCommit(). - Covers
transaction()on both MongoDB and PostgreSQL and MongoDB'sbeginTransaction(),
including retried MongoDB attempts. The "Work scheduled inside a transaction" section of the
transactions guide describes the behavior.
5. Upgrade notes
- Update the family. Run
npx warlock update. - Read what changed. Run
npx warlock release-notes --from 5.39.1. - Run
npx warlock check. For eachWL9025warning, decide whether the update should
replace the column. If it should, useset({ column: value })orset("column", value).
If merging nested keys is intended, keepmerge(). - Replace app helpers (optional). A helper that calls
set()once per column can become
model.set(values). - Keep
afterCommit()workarounds. Code that already moves late work intoafterCommit()
needs no change; it is still the right tool for work that must follow a COMMIT.
Warlock.js 5.39.1
Released 2026-10-26.
All 33 packages (@warlock.js/* and create-warlock) move to 5.39.1 together;
keep every family package on the same version. No breaking changes. This is a fix
release for test-run exits, migrations, standalone builds and log channels.
1. Breaking changes
- None. 5.39.1 has no breaking changes.
2. Test runs exit promptly (core, scheduler)
- Running schedulers are stopped on shutdown. An app that calls
scheduler.start()
while its modules load (the stock template does this) kept the Vitest main process
alive after the HTTP test server stopped. Vitest then waited out its 10-second close
timeout and printedsomething prevents N Vite servers from exiting. The shutdown of
the HTTP test server,warlock startand per-worker test teardown now stop every
running@warlock.js/schedulerscheduler withinapp.shutdownTimeout. scheduler.shutdown()clears its own cap timer when the jobs finish first, so it
no longer holds the process open for up to 30 seconds.- If the message still appears, a handle the app opens itself (an interval, a socket, a
queue worker) is keeping the process alive.npx vitest run --reporter=hanging-process
names it.
3. Migrations close cleanly and never stall silently (core, cascade)
warlock migratereleases the migration lock and closes every data source before
it exits: on success, on failure, with--pending, and onCtrl+C/SIGTERM(exit
130 / 143). Shutdown has a bounded wait, so it cannot hang.- Connecting to MongoDB is bounded (30 s for server selection) and fails with a clear
message instead of hanging atConnecting to database. A stalled graceful close falls
back to a forced close after 10 s. - Long migrations report progress. A migration that runs long prints
still running "<name>", step N of M, <n>s elapsedat 30 s and then every 60 s.
Migrations are not aborted by default, so a long index build is never left half-applied. - Opt-in hard limit.
warlock migrate --timeout <seconds>(or
WARLOCK_MIGRATION_TIMEOUT_MS, in milliseconds) fails a migration that exceeds the limit,
naming it, and exits 1. - Partial failures are explained. A failed migration names the migration and, on
MongoDB, the step (step 3 of 5). It says that no ledger row was written, how many steps
were applied, and how to recover: re-running is safe because index creation is
idempotent, or--rollbackstarts over.
4. Standalone builds (core)
- Vite is no longer bundled when the app lists it under
devDependencies. Only
dependencies,optionalDependenciesandpeerDependenciescount as declared by the
app. This removes about 60 MB of bundled Vite and therolldownandlightningcss
native addons fromdist/. - Build-time tools stay out of the bundle.
vite,rolldown,lightningcss,
esbuild,oxc-parser,@vitejs/*and@babel/*(except@babel/runtime) are never
bundled or staged when framework code imports them, even when the app lists them under
dependencies. A third-party library that imports one at module level is still bundled,
so it keeps working with nonode_modules. - Aliased native packages are staged under the imported name. A package installed as
"node-pty": "npm:@lydell/node-pty"is copied todist/node_modules/node-pty, so the
import resolves fromdist/. - Only reachable native packages are staged, and the build warns about a native package
it could not copy because itsos,cpuorlibcexcludes the build machine.
5. Log channels (logger)
LogChannel.readyresolves once the channel's deferredinit()has finished. Await
it before relying on the channel being set up.- A channel disposed before its deferred
init()ran no longer creates its directory or
arms the 5-second flush interval afterwards.
6. Upgrade notes
- Update the family. Run
npx warlock update. - Read what changed. Run
npx warlock release-notes --from 5.39.0. - Remove workarounds (if added). Drop any
build.externalentries forvite,
rolldownorlightningcss, any manualscheduler.shutdown()call in test teardown,
and any raised VitestteardownTimeoutadded for slow exits. - Set a migration limit (optional). In CI, pass
warlock migrate --timeout <seconds>
if you prefer a failed run to a slow one.
Warlock.js 5.39.0
Released 2026-10-26.
Keep every family package (@warlock.js/* and create-warlock) on the same version.
There are no breaking changes in this release. Two changes alter behaviour without a
type error; review them before upgrading (section 1).
1. Breaking changes
- None. 5.39.0 has no breaking changes.
Behaviour changes to review
--versionand-vprint the Warlock version only when no command is given. A
command can take its own--versionoption (core).- Core-level refusals on page hosts now render the site's page for HTML requests
(core,web). AGETorHEADrequest that namestext/htmlinAcceptgets the
page for a refused path such as/public/../x(403) or a malformed URL (400). API clients
keep the JSON answer.
2. Standalone builds and fewer files (core, web)
-
Single bundle for web pages.
build.singleBundlenow works with web pages. The server
pages and routes are inlined into onedist/app.js, andnode dist/app.jsstarts from a
copy ofdist/with nonode_modulesabove it. A copy ofdist/also works from any
working directory, because its client folder is found next to the running bundle. -
Native addons are staged automatically. A package with a
.nodebinary stays external
and is copied intodist/node_modules/<package>with only what it needs to load. The build
report names each one. -
Optional integrations are detected. A lazy
import()orrequire()of an optional peer
or optional dependency that a framework package lists, and the app does not declare, stays a
lazy runtime import that never runs. The build report names it, so no hand-written
externallist is needed. An import the app makes, or a package the app declares, is still
bundled or fails the build. -
Build-time tools are left out. Vite, the development server, the page scanner and the
Babel parser are not in the production bundle, and the server never loads Vite. The native
parseroxc-parseris replaced by a module that throws if it is ever called. -
Production React. A
singleBundlebuild setsprocess.env.NODE_ENVto"production"
insidereact,react-domandscheduleronly, so React's development build is dropped.
Aprocess.env.NODE_ENVentry inbuild.defineturns this off. -
Client build options.
webConnector({ build: { chunks, precompress } })controls the
client output:chunks: "split"(the default) writes a React vendor chunk, the entry, a lazy chunk per
page and shared chunks.chunks: "merged"keeps the same lazy page loading and packs small shared chunks together
by size, which gives far fewer files.chunks: "single"writes one JavaScript entry and one stylesheet, with dynamic imports
inlined. It is for single-site apps only.precompress: falseskips the.brand.gzcopies of each client asset (defaulttrue).
Final-bytes chunk names, dedupe, the integrity check, preload lists and payload budgets work
in every mode. -
Desktop and offline recipe. The desktop and offline app recipe sets both
chunksand
precompress.build.singleBundlealone does not change them. -
Dist file summary.
warlock buildprints the number of files and the total size of
dist/when it finishes.
3. Release notes in every install (core)
warlock release-notesprints the family release summaries that ship inside the package
(releases/<version>-summary.md, from 5.30.0 onward). It reads files only, so it needs no
network and no booted app.- With no arguments it prints the installed version's summary.
--from <version>(optionally with--to <version>) prints every later release, oldest
first, led by a combined list of breaking changes.--version <v>prints one release,--listlists the versions with dates, and--json
prints the same data as JSON.- A version the package does not ship is reported with its GitHub release URL.
- Pointers from upgrade and update.
warlock upgrade,warlock upgrade --checkand
warlock updateend withWhat's new: npx warlock release-notes --from <old>. TheAGENTS.md
section thatwarlock agents-mdgenerates names the installed version and the command. - Agent skills. The
update-packages,deploy-releaseandupgrade-from-5-29skills tell
agents to runnpx warlock release-notes --from <current>before upgrading.
4. Tests and tooling (core, web)
- Config files no longer walk the app graph. Loading config registered each config file
together with its whole import graph, so a config that imported an application service made
every test worker spend minutes in setup (about 480 seconds for a 3,000-module graph, measured).
Config files now register with their direct import edges only. The dev server still builds the
full graph from its own scan. onPageError(hook)(web, from@warlock.js/web/connector) registers a server
error-report hook at runtime, from a module'smain.ts. It receives the same
(error, context)asweb.errors.report, runs through the same bounded queue, and returns a
function that removes it. Use it instead of importing application code intosrc/config/web.ts.
The typesPageErrorHook,PageErrorInfoandPageErrorKindare exported.warlock doctorcheckconfig-app-importswarns when a file undersrc/configimports,
statically or withimport(), anything undersrc/app. The message names the file and the
import. Type-only imports are not reported.warlock upgrade --checknpm fix. npm 10.9 crashed on the family's lockfile with
Cannot read properties of null (reading 'edgesOut'), which skipped every later step. When the
npm on PATH is older than 11.6.2, the scratch install now runsnpx -y npm@11.6.2, and--from
is inferred from the installed@warlock.js/core.- WL9024 text.
WL9024applies to any integration spec: a*.spec.tsor*.test.tsfile
that is not*.unit.*, or any file undertests/. Its fix text now says to rename the file to
<name>.unit.spec.tsoutsidetests/. The 5.38.0 note said*.test.tsto*.spec.ts, which
was wrong.
5. Models (cascade)
- System columns in typed
get()andset(). On a model declared with a schema type, these
columns are accepted without being declared:id(number),_id,created_atand
updated_at(Date),deleted_at(Date | null), and their camelCase spellings. A column the
schema declares keeps its own type, so a renamed timestamp or a stringidbelongs in the schema. - Clearer
set()errors. A wrong value type is reported as a value mismatch, not as a
misleading error about dot paths. - Typed runtime key. A column named at runtime is set with a key typed
K extends ModelKey<Schema>. The newModelSystemColumnsandModelColumnstypes are exported.
6. Web UX (web)
- Refusal pages. For a
GETorHEADrequest that namestext/htmlinAccept, a 401 or
403 renders the site'sforbidden.page.tsx. Any other refusal renderserror.page.tsx, and
then the framework fallback page, with the refused status. In multi-site mode the site is
chosen by host, as for any page. API clients keep the JSON answer.
7. New apps (create-warlock)
- Installs on npm 10.9. A fresh scaffold no longer crashes on npm 10.9 (the npm bundled with
Node 22) withCannot read properties of null (reading 'edgesOut'). The template pins Vitest
5.0.5, because the Vitest releases before it trigger that npm bug on their own. TheedgesOut
hint stays as a last resort.
8. Upgrade notes
- Rehearse first. Run
npx warlock upgrade --check. It runs the upgrade on a scratch copy and
never writes the real project. It reports the breaking changes that affect the app and the
outcome of each step. - Update the family. Run
npx warlock update. It ends with a pointer to the release notes. - Read what changed. Run
npx warlock release-notes --from 5.38.0to print the summaries of
every release after 5.38.0, with the combined breaking-changes list first. - Review the two behaviour changes in section 1:
--versionand-vwith a command, and
browser requests refused by core on a page host. - Opt in to the standalone build (optional). Set
build: { singleBundle: true }in the app's
warlock.config.ts, then runnpx warlock build. Read the build report for the native addons,
optional integrations and React notes, and check the file count and size that the build prints.
Test the output by runningnode dist/app.jsfrom a copy ofdist/. - Choose the client chunk mode (optional). Set
webConnector({ build: { chunks, precompress } })
in the web connector to pick"split","merged"or"single"chunks, and whether to write
compressed copies. For a desktop or offline app, follow the desktop and offline recipe, which sets
both options. - Use
onPageErrorfor error reporting (optional). Ifsrc/config/web.tsimports application
code only to report errors, register the hook withonPageError(hook)frommain.tsinstead and
remove the import. Runwarlock doctorto check forconfig-app-importswarnings. - Rename integration specs (if flagged). A
WL9024warning names an integration spec that
imports nothing needing the app. Rename it to<name>.unit.spec.tsoutsidetests/. - Update typed model code (if needed). Columns that the schema does not declare are now
accepted only when they are system columns (section 5). Other columns still need a schema entry or a dot path.
Warlock.js 5.38.0
Released 2026-10-25.
All 33 packages (@warlock.js/* and create-warlock) move to 5.38.0 together;
keep every family package on the same version. One change is breaking for typed
models (see section 1). The new defaults for test runtimes and the module boundary
apply to new apps or to new config only; existing apps keep their own settings.
1. Breaking changes
- Typed model
get()andset()reject unknown columns (cascade). For a model
declared with a schema type,get()andset()with a top-level column name that is
not in the schema are type errors. Add the column to the schema, use a dot path
("profile.city"), or declare the model without a schema type to keep loose access. - Nothing else in 5.38.0 is marked breaking. The changes below change behaviour
without a type error. Review them before upgrading:- A 5xx is logged as one record with a new action name (section 2); log queries or
alerts that match the old entries need updating. - A 401 or 403 on a page route renders the application's forbidden page (or the
framework page) instead of a raw JSON body (section 7). web.moduleBoundarydefaults to"warn"for existing apps (section 4).
- A 5xx is logged as one record with a new action name (section 2); log queries or
2. Request log (core, devtools, logger)
- Persistent request log. With
http.logging.requestson, each request writes one
redacted JSON record after the response is sent: id, trace id, method, URL (credential
query values masked), route, params, query, headers, session user, IP, status, duration,
phases, log entries, a query summary (count, slowest query, N+1 findings) and, for a
5xx or a thrown request, the error with its stack, phase, page file and site. - Defaults. On in development and off elsewhere; in development every request is
kept, elsewhere only errors. Records go to a daily file understorage/logs/requests/
by default.slowMs,sampleRate,include(headers, body, query, params, session,
response, stack, logs, queries, phases),maxBodyBytes(16 KB by default) and
retention: { maxDays, maxSizeMb }tune it. Outside development only allowlisted headers
are recorded, and bodies are left out unlessinclude.bodyorinclude.responseis set. - One error record per 5xx. The three earlier error entries (
request-error,
model-write-validation-error,unhandled-request-error) are replaced by one error-level
record withmodule: "http"andaction: "request.<phase>", logged once per request. A
returned503is treated as an intentional answer and is not logged. A 4xx is never logged
as an error. The per-requesthttp.logline is unchanged. - Warlock CLI and MCP.
warlock logs requests(filters--status,--route,--since,
--limit,--json) andwarlock logs request <id>read the log without booting the app.
The MCP toolrequest_logis read-only. Arequest-logskill ships with the release. - Devtools. The Requests panel reads the persisted log, so history survives a restart,
and it filters by status, route, method, time and text. It copies any request as curl,
with redacted values left as[REDACTED]. - Agents tab. The development dashboard at
/__warlockshows per-turn agent traces
(section 5). It needs no Panoptic.
3. Tests (core, create-warlock)
- Unit and integration projects.
*.unit.spec.tsand*.unit.test.tsrun in a Vitest
unitproject with no setup files and no app boot. Every other spec, and anything under
tests/, runs inintegration.guardUnitProject()fails a unit spec that imports a
connector, the connectors manager, bootstrap or a test-server helper. - Worker lifetime for new apps.
warlock add testand the starter set
lifetime: "worker"andresetDatabase: trueinsrc/config/tests.ts, and use one
database per Vitest worker.WARLOCK_TEST_LIFETIME=fileswitches back. Existing files
are never overwritten. - Per-file reset in worker lifetime. Open test HTTP servers are closed, the request
context is cleared, the route table returns to the boot snapshot, and a real in-memory
cache is flushed.setupTest({ lifetime: "file" })opts one file out of the shared
runtime. - Boot profile.
tests.profileSetup: trueorWARLOCK_TEST_PROFILE=1prints per-phase
timings, including each connector.WARLOCK_TEST_PROFILE_JSON=<path>writes them as JSON. - Fixes. The HTTP test server registers the connectors
warlock.config.tsdeclares, so a
page request no longer answers 404. A test run that booted the web connector exits cleanly
(Vite, its HMR websocket and its watchers are closed on shutdown).
4. Build (web)
- Payload budgets.
warlock buildmeasuresbuild.budgets.payloadfor each page's
translations and browser route table. A page over its limit fails the build with
mode: "error", or prints a warning withmode: "warn". - Client chunk integrity. Each build writes
client/.vite/warlock-integrity.json. The
next build fails, naming each file, when a file name now maps to different bytes. The
previousBuildoption points the check at another build, such as the deployed release. - Stable chunk names. Production chunks are named after their final bytes. Byte-identical
non-entry chunks are merged, and the manifest, preload lists, budget report and.brand
.gzfiles use the final names. - Module boundary. The client build enforces
web.moduleBoundary. Browser code may
import only browser-safe files (*.client.ts(x), schema and type files, the module's
web/folder and non-code files)."warn"is the default,"error"fails the build and
"off"disables the check. The starter sets"error". - Two tracing phases.
translationsandnamed-api-routesare reported through core's
TracingHooks.onPhase.
5. Validation and AI (seal, core, ai)
- Readable field names in validation messages. A field without a translated label is
named in words:max_dumbbell_kgreads "Max dumbbell kg". Labels underattributes.<field>
still win.validation.humanizeAttributes: falsekeeps the raw key. - Label check.
warlock doctorcheckvalidation-labels(WL4002) is off by default
and lists request-schema fields without a label whenvalidation.warnMissingLabelsis on. - Schema helpers.
humanizeAttribute(key)is exported fromseal.
ObjectValidator#partial()keeps its types..internal()marks a server-set field, and
withoutInternal()andinternalKeys()remove or list those fields. - OpenAI strict JSON Schema. An optional enum, literal, union or
$refis nullable in
toJsonSchema("openai-strict"), so a client can sendnull.draft-2020-12and
openapi-3.0nullable output is corrected too. - Tool schema lint.
ai.tool()checks its schema against OpenAI strict, Anthropic and
Google. In development it logs one warning per tool.lint: falseor per-tool options
tune it.describeToolSchema()anddescribeToolSchemas()return findings and an
estimated token cost. - Tool-use contract (opt-in). Agents can set
actWhenAsked,onToolRefusaland
backstops. Agents without them behave as before. - Eval scorers.
ai.eval.requiredToolCalled()andai.eval.changeClaimedWithoutToolCall(). - Agent traces. Each run records system prompt hash, tools offered, calls, refusals,
retries, usage and cost. Traces are on in development and off under test and in
production.tracesets this per agent. Traces are redacted and kept in an in-memory
store.diffAgentTraces()andformatAgentTrace()help compare runs. - CLI.
warlock ai toolslists per-tool token estimates and schema findings, and
warlock ai tracelists or prints agent runs from the running dev server.warlock ai
no longer marks--modelas required in its option list.
6. Core tooling (core, logger, create-warlock)
warlock upgrade --checkrehearses an upgrade on a scratch copy of the app and never
writes the real project. It reports the breaking changes that affect the app and the
outcome of each step. Child processes run one at a time with a memory cap
(--memory-mb, default 4096) and a time limit (--timeout-minutes, default 20).warlock doctorsitescheck renders each configured site's root page in
development and fails (WL5005) on any 5xx.- Capabilities.
defineCapability,provideCapability,useCapability,
hasCapabilityandlistCapabilitiesgive a module-cycle-breaking slot a typed home.
useCapabilitythrowsWL2008when nothing provides it.warlock doctorwarns about a
required capability with no provider. Ause-capabilitiesskill ships with the release. warlock inspect --section wiring(and the MCP toolinspect_wiring) lists every
auto-loaded file with its load order, then every capability slot with its provider and
consumers.- Package manager detection follows the
packageManagerfield, then the manager that
installednode_modules, then the lockfile that matchespackage.json. A stale workspace
file no longer wins.warlock updateprints the manager it chose and why. warlock agents-mdlists the skill of a package that ships a singleskills/SKILL.md
(the AI provider packages and@warlock.js/sitemap).- Logger.
LogChannel.passivemarks a channel that only observes entries. The terminal
fallback used when no log channel is configured ignores passive channels. - Loading the config keeps
@warlock.js/*imports external, so a monorepo checkout no
longer loads a second copy of core.
7. Web UX (web, create-warlock)
- Forbidden page.
forbidden.page.tsx(with an optionalforbidden.setup.ts) is the
page for a refused request. A 401 or 403 on a page route renders it with that status and
receives{ error, status }(plusdebugoutside production). The starter ships one, and...
Warlock.js 5.37.1
Released 2026-10-24.
All 33 packages (@warlock.js/* and create-warlock) move to 5.37.1 together;
keep every family package on the same version. No breaking changes. Upgrade notes
below are optional clean-ups.
1. Multi-site pages in development and tests (web)
- Pages no longer answer 500 in multi-site apps under the development server and
the HTTP test server. The browser route table is read from Core's router, never
from a site's registration facade. Withweb.namedApiRoutes: falsethe table is
not computed at all, and a failure while building it is logged with its own phase.
2. Every 5xx page failure is logged (web, core)
- One error record per failed request, at error level, with the error message,
the source-mapped stack, the phase (translations, module load, middleware, session,
loader, metadata, render, stream, deferred data, error page), the page file, route,
site and request id. 4xx outcomes stay out of the error log. - Development and test error pages show the same details in a panel; production
stays generic.error.page.tsxreceives them asprops.debugoutside production. - Translation lookups that ran before the handler's error handling now report as the
translationsphase instead of a bare 500.logis exported from@warlock.js/core.
3. Upgrading the family with npm (core)
warlock updateon npm removes the family's (and Vite, Vitest,@vitest/*,
@vitejs/*) lock entries and re-resolves them in one install, so an existing
package-lock.jsoncan step to the new version. npm 11.6.2 or newer is required for
that step; older npm gets a warning naming the minimum. Family packages keep their
exact pins on each other.WL8010:warlock checkreports installed@warlock.js/*packages whose
versions differ.warlock upgradeno longer rewrites packages that are already newer than
--to, and its install hints name the package manager the project actually uses
(thepackageManagerfield, then a workspace file, then the newest lockfile).
4. Tests
- Vitest 5 duplicate-plugin warning fixed. A config that lists
lowerStage3Decorators()in the root and again in inline projects no longer warns.
Thevitest-inline-project-pluginsupgrade codemod lists repeated calls for review. - Worker lifetime for HTTP and database suites.
tests.resetDatabasetruncates
tables or collections before each file in worker lifetime (keeping the migration
ledger);resetTestDatabase(),workerDatabaseName()andtestWorkerId()come from
@warlock.js/core/tests; test HTTP servers a file leaves open are closed at the
next file.warlock add testwrites a projects layout where
WARLOCK_TEST_LIFETIME=workerturns worker lifetime, database reset and
isolate: falseon together, and the setup profiler recommends worker lifetime when
setup takes at least half of a run.
5. Development server (web)
- The HMR websocket attaches through Vite 8's
server.ws, so the
server.hmr.*deprecation warning is gone. - The client-bundle security check no longer reports the app's own
index.html
entry as an unjudged import.
6. Verification
The publish run repeats the full family gate (typecheck, tests, build, pack,
local-registry install and the blog app) before anything reaches npm, and checks the
blog against npm after publishing. Format, lint, public text, spec coverage, MDX,
shipped-docs and skill reference checks ran clean locally.
Warlock.js 5.37.0
1. Breaking changes
- Document-store rollbacks no longer erase fields by default (
cascade). On MongoDB,
a plaindropColumn()ordropColumns()insidedown()is skipped with a warning
instead of running$unseton every document. Forward drops (up(),drop: [...])
are unchanged. To erase the data on purpose, pass{ destructive: true }. - Add-only alters derive their rollback (
cascade). AMigration.alterthat only adds
columns or fields no longer needs a hand-writtendown(). Delete thatdown()so the
rollback is derived; on SQL it drops the added columns, on document stores it only
updates the ledger. - Rollback and fresh migrations ask first (
core,cascade).warlock migrate --rollback,
warlock migrate --fresh,cascade migrate:rollbackandcascade migrate --freshprint a
destructive-operation summary. Outside development (Warlock's environment forwarlock migrate;
NODE_ENVother thandevelopmentfor thecascadeCLI), a rollback that removes data asks for confirmation and
refuses without a terminal. Scripts and CI jobs must pass--yes(or--force). A rollback
that removes no data does not ask. ProgrammaticrollbackMigrations()andfreshMigrate()
do not prompt; callconfirmRollback()first to get the same guard.
No codemod is needed for 5.37.0. The changes above are manual: review the add-only
down() methods, pass { destructive: true } where erasing data is intended, and add
--yes to deploy scripts.
2. Named API route table controls and guard detection
web.namedApiRoutescontrols what each page embeds.falseembeds no named routes, so
a named-route call throws (in development the error names the option; explicitpathand
methodstill work). An allowlist of route names withprefix.*patterns, or a filter
function(route, { site, guest }), narrows the table.{ include, privatePrefixes }
combines both. Every form can only remove routes from what the page's client code reaches.privatePrefixeskeeps every route under the listed path prefixes (for example
/api/admin/or/api/webhooks/) out of every table, for any viewer and any site.
Prefixes match whole path segments.- Per-site routes. The
sitesoption on a route, or onrouter.group({ sites }), names
the multi-site keys that may offer it in the browser. A route with nositesis shared by
every site, and a route's own value replaces its group's. - Guarded routes for guests. A route with an auth guard in its middleware chain (group
middleware included) is left out of the table for a guest. A viewer counts as a guest only
whenweb.sessionis configured and resolves no user. Withoutweb.sessionthe viewer is
unknown, and guarded routes stay in the table. - Guard detection in route inspection.
warlock inspectand the generatedAGENTS.md
list of mutating routes without an auth guard now count middleware that declares itself a
guard withmarkAuthGuard(), including one registered on arouter.group.warlock check
(WL9006) handles inlinemarkAuthGuard(fn, false)and local guard factories used as
middleware: [requireAdmin()]. - Types and seams. The new option types are exported from
web. The server seam
resolveNamedApiRoutes(names, options)takes{ config, site, guest }.
3. Safer migrations on document stores
- Rollback plan.
migrationRunner.planRollback(batches)lists, per migration, the
collections or tables dropped or truncated and the fields or columns dropped or unset,
with document and row counts. The counts come from new optionalcountRowsand
countWithColumnsdriver methods (MongoDB;countRowson Postgres). - Confirmation.
confirmRollback({ all, batches, yes, development })prints that plan and
asks outside development. The CLI rollback and fresh commands accept--yes/-yand--force. - Guards in hand-written
down().Migration#isDocumentStorelets a migration skip a
SQL-only drop on a document store.
4. warlock release for application deploys
warlock release is a deploy pipeline for applications, configured by a new release
section in warlock.config.ts:
candidatefreezes a clean commit into.warlock/releases/<version>-<shortsha>/with
a manifest (version, commit, time, Node version, lockfile hash).gateruns the named checks in order (default: typecheck, check, test, build) and
stops at the first failure.migrations --proveruns the pending migrations for real and checks the ledger, tables
and indexes against the expected set.stage, thenactivatechecks the staged files, the gate and that the version is
newer than the active one, switchescurrentwith an atomic symlink, keepspreviousand
runs the restart command you configure. Warlock never calls a process manager itself.verifychecks the version and commit reported atrelease.verify.url.rollback
switches back and reruns the restart command; it does not roll migrations back.list,statusandprune --keep <n>(the only command that deletes releases).
Every action accepts --json, and each release keeps a manifest plus a shared release.log.
5. Eval repeats and variance-aware gating (ai)
agent.eval({ repeat })runs every case several times (default1). Results carry pass
counts, per-run results and score and pass-rate intervals. Reports add overall and
per-category statistics (EvalCase.category), plusstableFailuresandflakyCases.
toJUnitshowspassed 2/3 runs.gate: "interval"flags a regression only when the baseline interval lies above the
current one by more thantolerance, so run-to-run noise no longer reads as a regression.
The defaultgate: "point"is unchanged.failOnStableFailuresfails the verdict when a
case failed every run.difftakes{ tolerance, gate, confidence, failOnStableFailures }as its third
argument; a plain tolerance number still works.EvalRegressionaddsregressedMetrics,
stableFailuresandflaky.- Baselines store the repeat count and intervals. Baselines saved by earlier versions
still load, and each of their cases counts as one sample.
6. Fixes and docs
- Validation messages (
core). The default validation translation no longer
returns a message with an unfilled:placeholder. The aliases:lengthand
:matchingInputare mapped, and a translation that still cannot be filled falls back to
seal's message. In development, the first unfillablevalidation.*key logs a warning.
App overrides keep working. - Test processes exit (
logger).FileLog's background flush timer no longer keeps the
process alive, so finished Vitest runs and CLI commands stop waiting on it.flush()and
flushSync()still write buffered entries. - Stylesheet imports in page discovery (
core). The dev loader answers stylesheet imports
(.css,.scss,.sass,.less,.styl) with an empty module, so sitemap discovery no
longer fails withERR_UNKNOWN_FILE_EXTENSIONon a page that imports one. - Hydration mismatch hint (
web). In development, a React hydration mismatch logs the
first mismatching node: its element path, the server text and the client text. Production
builds are unchanged. - Intl guidance (docs). The
create-a-pageandlocalize-pagesskills and the
localization and client/server boundary docs warn thatIntl.DisplayNames,
localeComparesorting and similarIntloutput can differ between Node and browsers and
cause hydration mismatches. They recommend a shared pre-computed table or rendering after
hydration.
7. Verification
Checks ran on Node 22.15, each through a memory- and CPU-capped runner:
- Typecheck and the strictness gate: clean.
- Full test suite: core 4,552 passed, web 3,411 passed, the other packages 5,682
passed, no failures. A new spec spawns Vitest on a fixture app in both test
lifetimes and asserts the process exits on its own. - create-warlock standalone, release-tool and workspace script tests: passed.
- Skill snippet, docs build, format, lint, public text, spec coverage, MDX and skill
reference checks: clean.
The publish run repeats the full family gate (typecheck, tests, build, pack,
local-registry install and the blog app) before anything reaches npm.