rk dev
The rk dev command sideloads an RSG 2.0 SDK app to a Roku device. It builds the app, installs it on the device, and launches it.
See the generated command reference for more information about arguments, options, and defaults.
The command was formerly named rk start-dev. The old spelling still routes to rk dev and prints the replacement on stderr, but it is deprecated.
Synopsis
The following syntax shows how to run the command:
rk dev [options]See the generated rk dev reference for more information about target selection, build flags, and option conflicts.
When you provide --config, the command resolves relative paths inside the config from the config file directory. As a result, one app root can hold multiple config variants. The following commands use two config variants:
rk dev --config roku-config-showcase.ts
rk dev --config roku-config-perf.tsSee rk logs for more information about persistent log collection, searching, and tailing.
By default, rk dev runs TypeScript semantic type checking before the build and deploy steps, and the command fails when it finds type errors. You can use --no-typecheck to skip this check and keep the previous behavior.
Without --watch, a build that succeeds but does not reach the device fails the command with exit code 1. The error code is SIDELOAD_FAILED when the install failed and LAUNCH_FAILED when the app was installed but did not launch. In the second case, the device holds the new build. The command prints the reason above the error.
The upload to the device's installer can take up to 2 minutes (30 seconds plus 1 second per MiB of package) on a Roku on your network, and up to 5 minutes (30 seconds plus 4 seconds per MiB) on a Roku Cloud Emulator device. Other installer requests on a LAN device time out after 15 seconds.
The following commands sideload a release-shaped build:
# Sideload a release-shaped build (minified, __DEV__ false, console dropped)
rk dev --release
# Same, with production manifest values (see note below)
rk dev --release --build-type productionThe --release flag changes only the shape of the code. It does not change the build type: rk dev still defaults to --build-type development; therefore, getBuildType() returns "development" and any manifest entries that depend on it (titles, hidden, and so on) keep their development values. You can pass --build-type production as well if you want a release-shaped build with production manifest values. The output still goes to dist-dev unless you set --out-dir.
You can combine --release with --no-sideload, --watch, --console, --dap-debug, and device-targeting flags. You cannot combine it with --no-build because there is no build to shape. The POST /api/start-dev route of rk server and the MCP deploy_app tool also accept release and reject the same flag conflicts. The REST route (POST /api/dev, alias /api/start-dev) answers 400 with the code INVALID_ARGS, and the MCP deploy_app tool returns an invalid-params error.
Sideloading without rebuilding
The rk dev --no-build command installs the artifact that is already in <out-dir>/pkg-dir instead of producing a new one. It replaces the top-level rk sideload command, which is deprecated.
The following commands show how to install an existing build:
# Install what the last dev build produced
rk dev --no-build
# Install a production build
rk build
rk dev --no-build --out-dir dist-buildThree things are worth knowing:
outDircomes fromroku-config.tswhen the project sets one. The install path loads project config the same wayrk buildandrk devdo, so a configured output directory is honoured rather than ignored.- A newer build elsewhere is reported, not silently preferred. With no
--out-dirand no configuredoutDir, the default isdist-dev. Ifdist-buildholds a newerpkg-dir, the command prints which artifact it is installing, which one is newer, and the flag that switches — then installs the one you asked for. Passing--out-dir, or configuringoutDir, suppresses the check, since the choice was then deliberate. - A missing directory names a command that can create it. The error for an absent
dist-dev/pkg-dirpoints atrk dev, notrk build, and reports anypkg-dirit did find elsewhere.
Options that only shape a build are rejected rather than ignored: --watch, --debug, --inspect, --inspect-persist, --drop-console, --drop-debugger, --lower-destructuring, --solid-dev-mode, --no-write-ts-path, --no-typecheck, --build-type and the --perfetto-* family. Device targeting (--device, --host, --password, --dap-debug) and launch options (--launch-params, --no-launch, --console) still apply.
Every install of a Hermes bytecode build (index.hbc, from --compile-ts), whether plain rk dev or rk dev --no-build, prints a warning: devices without the rsg_typescript device token refuse to launch it. The install still proceeds. The MCP sideload_app tool reports the same text under warnings.
The --no-build flag always runs locally. The server's build endpoint does not have an equivalent; therefore, the CLI declines the server fast path for it.
Building without a device
The rk dev --no-sideload command is the inverse of --no-build. It produces the dev-shaped artifact and stops before the sideload.
The following commands show how to build without a device:
# Dev build, no device involved
rk dev --no-sideload
# Somewhere other than dist-dev
rk dev --no-sideload --out-dir dist-ci
# Rebuild on every change, still without a device
rk dev --no-sideload --watchOutput still goes to dist-dev unless --out-dir says otherwise, and the artifact is byte-for-byte what a normal rk dev would have installed — only the upload is skipped. That makes it the flag for a CI job that wants to prove the dev build compiles, for producing an artifact to inspect or archive, and for working with no Roku on the network.
No device is resolved at all. This matters because ordinary rk dev exits non-zero when no device is active and no password is stored, before it ever reaches the build. --no-sideload never gets that far, so it needs neither.
Because nothing is contacted, the device-facing options are rejected rather than ignored: --device, --host, --password, --dap-debug, --launch-params, --no-launch, --console and --auto-console-close. Every build option still applies, --watch included.
--no-build and --no-sideload are opposites — one skips the build, the other skips the sideload — so passing both is rejected outright. Like --no-build, --no-sideload declines the server fast-path and runs locally.
rk buildis still the right command for a production package.--no-sideloadproduces the dev shape (dist-dev, wired for the dev loop), just without installing it.
Watch mode
rk dev --watch rebuilds when the app's source or the static assets directory (./pkg by default) changes, and re-sideloads after each build.
In a terminal, pressing r runs a full rebuild: the output directory is cleaned, the app is built again and installed. Ctrl+C stops the watch. This works the same when rk dev hands the work to a running rk server, which also offers it to other clients as POST /api/dev/:sessionId/rebuild, with the session ID from the session-attach event.
It also rebuilds when roku-config.ts or roku-dev-config.ts is saved, including a roku-dev-config.ts created after the watch started. The config is read again and the next build uses it, so edits to app (manifest entries, build variables, logger levels), hermesConfig, dtsOptions and pipeline take effect without restarting. The cycle is reported like any other rebuild, and pipeline hooks receive isRebuild: true.
- The device is not re-resolved. Which device the build installs to comes from the devices store and
--device/--host, never from the config file, so a reload does not change it. - Some changes need a restart.
outDir, andapp.staticAssetsDirorapp.brsOnlywhen they change what is watched, are not applied during a session.rk devprints a warning and keeps the old value. - A config that fails to load is not applied. If
roku-config.tsorroku-dev-config.tshas an error,rk devprints it, keeps building with the previous config, and applies the file on the next save that loads. Aroku-dev-config.tsthat cannot be loaded is only skipped on the first build; on a reload it is an error. - Only the config files themselves are watched. A file the config imports is not.
Using Roku Cloud Emulator devices
rk dev installs on a running Roku Cloud Emulator device like on any other: you can make it active with rk device set-active rce:<name>, or you can pass --device rce:<name> for one command. A stopped one is not started unless you ask:
# Start the active device, build while it boots (usually a minute or two), install, launch
rk dev --start
# Same, then keep watching; the device stops when rk dev exits
rk dev --start --watch
# Cap this run at 15 minutes instead of the registration's maximum runtime
rk dev --start --max-runtime 900- Without
--watchor--console, a device--startstarted keeps running afterrk devreturns, untilrk device stopor its maximum runtime (--max-runtime, else the registration's, else 1 hour).rk devprints when that is and the most it can cost. - With
--watch, or--consolewithout--auto-console-close,rk devowns the device and stops it when it exits — unless another command is still using it. - If the build or the install fails after
--start, the device is stopped at once, since nothing usable was installed. A launch that fails after the install (LAUNCH_FAILED) leaves it running. - Ctrl-C stops a device
rk devstarted and owns, and exits with 130 (143 forSIGTERM). A device anotherrkprocess attached to meanwhile keeps running for that process. - A device that was already running is used and never stopped;
--max-runtimedoes not change its run, andrk devsays so. --startis refused with--host,--dap-debugand--no-sideload, for a LAN device, and insiderk server.
--dap-debug does not work on RCE devices. --console reconnects if the connection drops, and ends rk dev when the run ends. --no-build installs on a running RCE device the same way.
Lifecycle and cost rules are in rce.md.
Waiting for the debugger
--inspect-persistrequires firmware 16.0 or later. On earlier firmware theinspectform field is ignored during sideload and the device will not persist debugger-wait mode.
--inspectis one-shot:rk devsideloads, then launches via ECP withinspect=true.
Subsequent launches from remote/ECP do not wait for debugger unless launched with inspect again.--inspect-persistis sticky:rk devsendsinspect=1in theplugin_installmultipart form.
The device persists this behavior for the installed dev channel, so subsequent remote/ECP launches also wait for debugger.- Running
rk devwithout--inspect-persistinstalls without theinspectform field, which clears previously persisted inspect state. - If both flags are provided, persistent mode wins.
Examples:
# One-shot debugger wait for this launch only
rk dev --inspect
# Persistent debugger wait for all future launches of the installed dev channel
rk dev --inspect-persist
# Persistent + no auto-launch (set sticky mode without launching now)
rk dev --inspect-persist --no-launch
# Clear persisted inspect mode on next install
rk devDecoding runtime errors
When a crash occurs on the device, the console output (port 8085) reports addresses inside the compiled bundle. You can use rk decode-stack to translate them back to your TypeScript source:
# Live decode: stream the console and annotate stack frames in real time
telnet <ROKU_IP> 8085 | npx rk decode-stack --stream
# Or capture the console, then decode
telnet <ROKU_IP> 8085 > device.log # Ctrl+C after the crash
npx rk decode-stack --file device.logSee the Decoding Runtime Errors guide for more information about source maps and output formats.
Related documentation
- Generated rk dev reference: Lists the arguments, options, and defaults for
rk dev. - rk logs: Describes persistent log collection, searching, and tailing.
- Roku Cloud Emulator devices: Describes the lifecycle and cost rules for Roku Cloud Emulator devices.
- rk decode-stack: Describes how to translate crash addresses to TypeScript source.
- Mapping runtime errors to TypeScript source code: Describes source maps and output formats.
Updated about 6 hours ago