Skip to content

Capabilities & limits

A black-box probe proves every capability below. Each one drives a real session over POST /new and WebSocket, and an HTTP 200 alone is not a pass. Run the suite against any live deployment:

Terminal window
BASE=https://nimbus-os.dev bun test:behavioral
  • ShellReal shell, 60+ Unix commands, persistent 10 GB filesystemworks
  • Runtimesnode, bun via Cloudflare workerd nodejs_compat (V8 + Node-API shim, not the upstream binaries)works
  • Runtimespython / python3 — CPython 3.13 on wasm32-wasi (script + -c + -m + stdlib)works
  • Runtimesruby / ruby3 — ruby.wasm-based Ruby 3.3 (script + -e + -r + stdlib)works
  • Runtimesclang — LLVM 8 → wasm32-wasi-nimbus, modern wasi-libc sysroot default, multi-TU + user headers + fopenworks
  • Runtimestsc — tsrs 0.18.0, the experimental Rust port of TypeScript 7.1 built to wasm, when the project installs no typescript (check + emit, no --watch); a project's own typescript runs as installedalpha
  • RuntimesInteractive REPLs — python, ruby, node, bun (see REPL for state semantics)works
  • Packagesnpm install against the live registry, with cross-session L2 cacheworks
  • Packagesnpm alias dependencies such as alias: "npm:<pkg>@<version>"works
  • Gitgit clone over HTTPS — chunked checkout engine; facebook/react (7,300 files) in ~28 s; 84,000-file worktrees materialize via bounded continuationworks
  • NetworkIn-session loopback networking — curl http://127.0.0.1:<port> reaches servers in other isolates; node server.js auto-promotes to a routeable resident processworks
  • NetworkDurable app URLs — a private preview URL per port that restarts the server on the next request after an eviction; nimbus expose <port> --public for a shareable link bound to the program; nimbus app list / url / rotate / remove; the same verbs in the SDK as box.appsworks
  • NetworkStreaming HTTP through the fabric — SSE / chunked bodies flow live (per-chunk) across the isolate boundary, loopback and external preview alikeworks
  • NetworkLarge files and HTTP Range through port previews — Range, If-Range, If-None-Match, If-Match, If-Modified-Since, If-Unmodified-Since and User-Agent reach the server whole; 206 and 304 come back with Content-Range, Content-Length, Accept-Ranges and ETag; a 210 MiB VFS file served by a Node static server downloads byte-exact, whole and by range, with the session's memory boundedworks
  • NetworkCross-origin isolated previews — an app that sends COEP (Emscripten -pthread: SharedArrayBuffer + Workers + Atomics) is isolated in its own tab, and in the preview pane after an isolated workspace reload when its CORP admits the workspace origin (path previews always; host previews with CORP cross-origin/same-site)works
  • ShellUnix permissions — real uid/gid ownership with EACCES enforced on read/write (including inside bash), durable st_mode, persisted chmod (octal + symbolic) + chown, exec-bit enforcement: ./binary runs only if executable (Permission denied, exit 126 otherwise), generic #! shebang dispatchworks
  • ProcessesMulti-isolate processes — client/server apps span facets (opencode runs as a serve + attach pair, each in its own isolate)alpha
  • Dev serversVite SPA dev server — full HMR to the preview iframeworks
  • Dev serverswrangler dev for single-file Workers; Workers + Static Assetsworks
  • SDKProgrammatic sandbox SDK — exec/files/runtimes/processes/ports/Proteus-style toolsworks
  • ProcessesJS agent CLI primitives — env/home, npm/npx, child_process.spawn/exec/execFile, piped stdio, streams, logsworks
  • ProcessesForeground attached npm-bin TTY tabs — stdin, resize, ANSI output, clean exit; Pi official installer and npm path probedalpha
  • PackagesPython package workflows — pip, PyPI pure wheels, requirements/constraints, curated pure source artifacts, numpy and MarkupSafe linked into the interpreter, Flask, python -m flask run, python -m http.server previewalpha
  • PackagesRuby package workflows — gem, bundle, pure gems, Rack/WEBrick previewalpha
  • AgentSession Agent — editor-workspace chat with Cloudflare OAuth / Workers AI and sandbox toolsworks
  • Packagesnpx <pkg> — first-class shebang + auto-install fallbackworks
  • Packagesnode_modules/.bin/* resolves and executesworks
  • ShellBinary file round-trip via fs.writeFileSync / readFileSyncworks
  • ShellSession recovery — WebSocket drop → reconnect preserves cwd, env, filesworks
  • RuntimesWASI preview1 — all 46 spec functions; outbound TCP via path_open('/dev/tcp/<host>/<port>') (JSPI); full poll_oneoff (fd / clock / socket subscriptions)works
  • Runtimeswasi-threads / pthreads — mutex, condvar, pthread_join, TLS, barriers, semaphores; cooperative, never parallelworks

Shares its capability catalog with the README capability table, covered by the behavioral suite intests/behavioral/.

Nimbus is built on Workers, Durable Objects, and WASM, not as a Linux VM. A workload that needs one of these missing parts belongs on a microVM or container sandbox (where Nimbus fits).

Not supported
Docker, apt, custom Linux images no container substrate exists
Native Linux ELF execution there is no ELF loader
Native platform CLI shards, manylinux wheels, native gems fail before import with an exact ABI diagnostic
Raw public TCP listeners inbound is HTTP(S) through session routes
Next.js dev server, wrangler pages dev explicit limits today
GPUs —
wasi-threads / -pthread refused at link time (design note)
Constraint State
One active terminal owner per session sequential reconnect/share preserves state
node/bun REPL declarations don’t persist workerd CSP; scripts unaffected; Python/Ruby REPLs fully stateful
util.inspect and console output of what V8 alone can read util.inspect and console run Node’s own inspect.js. A promise’s result, an iterator’s or a WeakMap/WeakSet’s entries, and a proxy’s target and handler are read as values through workerd’s inspect and formatted by inspect.js. A proxy among them is shown by its target and handler, but a program’s code is never handed a rebuilt one: without showProxy, one whose target has a util.inspect.custom shows as <unknown> (Node calls that with the proxy as this), and a proxy inside it is shown by its innermost target without running its traps. A promise, iterator or collection whose own Symbol.toStringTag is a getter, whose class has its own Symbol.hasInstance or a name getter, or that has a proxy on its prototype chain, shows what it holds as <unknown> or <items unknown>: reading it would run that code again
Where an uncaught error was thrown node’s report of an uncaught error is Node’s: the file, line and caret, the error as util.inspect shows it, Node’s version, the exit code. The place is read off the error’s stack, since a Worker cannot read V8’s message: an error a builtin throws, which Node places at a line of its own library, is reported without one; a thrown value that is not an error, an error thrown away from where it was made (throw e), or one whose stack was replaced, has no place, or the place it was made at
ES modules under node run compiled to CommonJS, their imports live, every line and column where Node has them but in a module nested past about a thousand levels, which esbuild compiles. An import read before its module has run (the temporal dead zone) and a cycle’s exact evaluation order are not Node’s: workerd evaluates an ES module outside the request, where the process’s synchronous file system cannot be read
.tsx and .jsx under node compiled and run; Node refuses them
TypeScript under node stripped (or with --experimental-transform-types, transformed) by amaro as Node does, then run in Node’s format for it. Where Node refuses a file an ES module imports (syntax it does not strip, a file under node_modules), the imports before it have run first. A .js specifier naming a .ts file resolves, where Node finds no module
Argument errors of the builtins workerd provides (zlib, crypto, buffer, url, path, …) they have Node’s shape (its class, code and stack header), but in 416 measured calls their wording or code differs from Node’s, they do not throw, or the function is missing (tests/fixtures/builtin-error-gaps.json). The methods of a Buffer, an EventEmitter or an AsyncLocalStorage, hot paths, keep workerd’s shape
process.emitWarning --disable-warning and --redirect-warnings are not read; NODE_NO_WARNINGS=1 and --no-warnings in NODE_OPTIONS are
75,000+-object clones index-pack CPU headroom in progress
git transport HTTPS only, no SSH
npm dependencies from git (github:u/r, npm init u/r) npm installs from the registry only; npm init with a git repository as its initializer is refused with EUNSUPPORTED
npm init npm 10.9.8’s package.json, questions and configuration; it reads a directory’s entries in sorted order (npm takes the first .js file for main and bin in the OS’s order) and does not run an init-module
npm configuration (.npmrc, npm_config_*, flags) read as npm 10.9.8 reads it, except that a relative path value (userconfig=./x) resolves against the Worker’s own directory rather than the shell’s, and a workspace root’s .npmrc above the nearest package is not looked for
A native wasm binary’s linear memory 128 MiB. Past it malloc returns NULL and the program reports its own allocation failure, instead of the session dying without one
opencode TUI serve half boots and streams SSE; attach half renders frames from a facet with its own memory envelope, still hardening
Stopping an app’s server (box.apps, nimbus app) soon after it starts, then starting it again the session can reset: sockets close and that start is lost; files and the app’s storage are kept. A Cloudflare facet-storage defect: measured 1 in 3 restarts when stopped as soon as it answered, 1 in 36 after 1.5 s idle
Hosted demo evaluation only; no SLA, may be reset, not for secrets

Verified live: Vite + React, the Cloudflare Vite Plugin, single-file Workers, Workers with Static Assets, npm and git workflows, Python and Ruby scripts, and clang. Astro, SvelteKit, and Remix use the Vite path. Nuxt has Nitro caveats.

A capability joins this list only after a behavioral probe proves it live. Until then it stays on the research page.