Every LATdx command, flag, and stream contract in one place.
Command entrypoint in this repo:
latdx <command>If latdx is not found, complete install/link steps in cli-quickstart.md.
Top-level commands
| Command | Description | Example |
|---|---|---|
latdx test run | Run Apex tests from file/dir or org mode | latdx test run -o my-org |
latdx test list | List test methods from file/dir or org mode | latdx test list -d src/tests |
latdx test impact | Show which test methods reach a set of changed classes (or a git delta via --base); no execution | latdx test impact --base origin/main |
latdx adapt | Experimental (requires LATDX_EXPERIMENTAL=1). Transform Apex file (currently placeholder behavior). | latdx adapt -f Foo.cls |
latdx boost | Experimental (requires LATDX_EXPERIMENTAL=1). Adapt Apex source for test acceleration (standard Salesforce Test API). | latdx boost -d src -o out |
latdx restore | Experimental (requires LATDX_EXPERIMENTAL=1). Reverse boost-mode adaptations and restore original Apex classes. | latdx restore -d src |
latdx clean | Experimental (requires LATDX_EXPERIMENTAL=1). Remove latdx-deployed classes (mocks, trigger handlers) from an org. | latdx clean -o my-org |
latdx validate | Deploy changed (or all) components, then run the affected tests, in one step. CI replacement for sf project deploy validate on sandboxes / scratch orgs. | latdx validate -o my-sandbox |
latdx uninstall | Experimental (requires LATDX_EXPERIMENTAL=1). Fully remove latdx from a Salesforce org (restore, clean, uninstall). | latdx uninstall -o my-org |
latdx config | Manage CLI configuration (set, get, list) | latdx config set channel latest |
latdx auth login | Sign in to LATdx via your browser. It shows a pairing code to confirm against the one in your browser before you approve, waits for sign-in, then on success prints a tidy summary that ends with your plan and seat. If a browser can’t be opened (SSH / headless), it prints the sign-in URL inline so you can open it yourself, no re-run needed. For CI/scripts, --token <jwt> skips the browser and --no-browser prints the URL to stdout. Signing in stores a signed identity token; plan/access is resolved from the site when commands need it. Key is read from LATDX_LICENSE_KEY env then ~/.latdx/license.key; command-scoped env keys are forwarded to the daemon for that run. Unlicensed or free-tier latdx test run is capped at 100 tests per invocation. | latdx auth login |
latdx auth logout | Clear the locally-stored license key. Idempotent. | latdx auth logout |
latdx auth status | Show the signed-in LATdx account + current plan. Live by default: it sends the stored JWT to /api/license/resolve, so if your plan changed since sign-in (joined or left a team, started or canceled a subscription) it updates to the current plan. A signed-in account with no paid plan reports Free and the 100-test CLI cap; a revoked or unrecognized token asks you to sign in again and exits 3. If the site is unreachable, the last successful signed access proof is trusted for 24 hours; without grace it reports Free. --offline reports the locally-stored token without a network check. Exits 3 when not signed in or when the token is revoked / unknown. | latdx auth status · --offline |
latdx auth use-url | Persist the LATdx site base URL used for auth and live license checks. This is mainly for staging/local installs; normal production installs leave the default as https://latdx.com. Explicit --base-url and LATDX_BASE_URL still override this per invocation. | latdx auth use-url https://staging.latdx.com |
latdx subscription status | Show the active plan and CLI test cap (alias: latdx sub status). Identity-focused detail lives in latdx auth status; this is the plan/billing view. Live by default; --offline reports the locally-stored token. A signed-out caller is reported as Free with the 100-test cap (exit 0); a token the site no longer recognizes exits 3 and prompts a re-login. | latdx subscription status |
latdx subscription upgrade | Open the LATdx pricing page in your browser to move off the free tier and lift the 100-test cap (alias: latdx sub upgrade). In a non-interactive shell or with --no-browser it prints the URL instead of opening one. (latdx upgrade is a different command that updates the CLI binary.) | latdx subscription upgrade |
latdx daemon | Manage the local daemon process (stop, restart, status) | latdx daemon status |
latdx cache | Cache management for the current workspace (status, clear) | latdx cache clear |
latdx mcp serve | Run the Model Context Protocol server over stdio so an AI agent (Claude Code, Codex, Cursor) can run Apex tests via LATdx. See Use LATdx with an AI agent. | latdx mcp serve |
latdx upgrade | Check for and apply CLI updates (alias: latdx update; --latest / --stable override channel for one run) | latdx upgrade check |
latdx -V --version | Print the brand banner: mint ANSI Shadow LATDX glyph + right column with LATdx CLI v<ver> (<channel>), a local user: <email> · access: run auth status identity row (or not signed in · FREE with no token), and a daemon: <state> · org: <alias> status row (probe capped at 100ms; degrades to daemon: ? unknown on timeout). Blank lines pad it above and below. With -o / --target-org also prints org reachability under the banner (exits non-zero if the org query fails). The banner also renders on bare latdx and latdx --help; live access is checked by auth status and commands that need it. Suppressed under --json / --quiet / --plain / NO_COLOR. | latdx --version -o my-org |
Global options
| Flag | Description | Example |
|---|---|---|
-C, --cwd <path> | Run as if latdx were started in <path> (like git -C). The CLI changes to that directory first, so the Salesforce project root, the daemon workspace (and its test-result cache), and any relative --file / --dir / glob inputs all resolve from there. Lets you run a project’s tests, with a warm cache, without cd-ing into it. Errors if the path is not a directory. | latdx -C ~/Projects/Teachiq test run -o my-org -n FooTest |
--no-color | Disable ANSI color escape codes. Also honored via the NO_COLOR environment variable (any non-empty value). | latdx --no-color test run |
-q, --quiet | Errors only. Wins over -v and the env-var level. | latdx -q test run |
-v (repeatable) | Increase verbosity. Default is warn. -v = info, -vv = debug-tier, -vvv = trace-tier (loudest). The global counter only applies before the subcommand name. | latdx -vv test run |
--plain | Disable spinners, live updates, and ANSI color output. Useful when running under a wrapper that does not support cursor control. CI auto-disables spinners and live updates but keeps ANSI color unless --no-color / NO_COLOR is set. | latdx --plain test run |
--json | Machine-readable mode. Log lines on stderr become NDJSON ({"ts","lvl","channel","prefix","phase","msg","fields"?} per call) and TTY effects (spinners, live updates, color) are forced off. Every leaf subcommand also emits its result payload on stdout as a single JSON document; the --json contract jest test enforces this for every command and is the fastest way to confirm coverage. | latdx test run --json | jq |
-y, --yes | Auto-affirm every interactive confirmation. Equivalent to setting LATDX_AUTO_CONFIRM=true for the duration of the invocation; useful for scripted or CI runs that must not block on stdin. | latdx -y test run |
Subcommands also accept their own --verbose boolean. The global counter -v / -vv / -vvv only applies before the subcommand name (e.g. latdx -vv test ...).
Environment variables
The most relevant env vars for the global flag surface:
| Variable | Description | Example |
|---|---|---|
LATDX_LOG_LEVEL | Explicit log level. One of error|warn|warning|info|debug|trace|off|silent (warning aliases warn; silent aliases off). Used when neither -q nor -v is passed. There is no --log-level flag by design: env-only keeps the global flag surface compact. | LATDX_LOG_LEVEL=trace latdx ... |
LATDX_LOG_FORMAT | Advanced override for the log-line format (text or json). The --json flag is the canonical way to flip this; the env var is for callers that want NDJSON logs without flipping a per-command payload-shape switch. | LATDX_LOG_FORMAT=json latdx ... |
For the full list of supported environment variables, see environment-variables.md.
Stream contract
LATdx separates machine-readable output from human diagnostics on the two standard streams:
- stdout carries the command’s result payload only. With
--json(where the subcommand supports it), exactly one JSON document; without--json, the human-formatted result. It is never mixed with logger output, spinners, live test renderer frames, or progress events. - stderr carries every diagnostic stream: logger lines (text by default, NDJSON when
--jsonis passed orLATDX_LOG_FORMAT=jsonis set), spinner animations, the live test renderer,--progress-events, and interactive prompts. - Auto-degradation (no flag needed): non-TTY stdout,
CIenv set, or--jsoncollapses TTY-only affordances such as spinners and the live renderer automatically. CI text output still uses ANSI color when available;--no-color/NO_COLORdisables it.
Pipes like latdx test --json | jq work without any extra flags.
test run
Startup banner
Non---json runs open with a single intro line carrying the resolved
org alias, then stream the test output:
test run • my-orgThe alias is either the literal --target-org value the user typed, or
the local default alias when bare-invoked. The intro emits before
workspace discovery or org-metadata fetch, so the header lands first.
Extra bullets appear only for env-driven anomalies the user did not type:
cache off (LATDX_CACHE_DISABLE=1)when the cache killswitch env is set,auto-confirmwhen the run is non-interactive without an explicit--yes(prompts will auto-yes instead of pausing).
Flags the user typed (--engine, --db, --cache off, --yes) and
stable defaults (cache phase, batch mode, anon mode) are omitted from
the header. Under --plain and CI providers the same line emits in
logfmt form (latdx test run • ...). A context step beneath it
carries the resolved engine / db / cache details for log scrapers.
--json suppresses the chrome so the stdout payload stays clean.
Phase ladder
On a cold daemon (first invocation after latdx daemon stop), the
chrome surfaces each one-time-per-daemon cost as its own phase, so it
is attributable rather than hidden under Preparing test run:
▸ test run • my-org
◐ Warming engine ← daemon spawn + apex-ls JVM init
✓ Warming engine 891ms
◐ Pre-hash tests 425/1336 ← workspace-wide Phase 4 prewarm (cold only)
✓ Pre-hash tests 5.52s
◐ Preparing test run
✓ Load org context 2ms
✓ Resolve tests 305ms
✓ Match cache keys 2ms ← in-memory lookup against the precomputed map
✓ Preparing test run ~310msOn a warm daemon (subsequent runs), both Warming engine and
Pre-hash tests go silent: the JVM cache is warm and the cache-key
map is populated. The ladder reduces to:
▸ test run • my-org
◐ Preparing test run
✓ Resolve tests 5ms
✓ Match cache keys 2ms
✓ Preparing test run ~10msFile edits invalidate the precomputed entries whose dependencies include the changed types; the next run recomputes only the impacted tests and replays the rest from the fast path. See ADR 0018 for the correctness contract.
The cache-marker legend (a dim-amber ↺ glyph means “served from
cache”) lives directly above the Summary block when at least one
test row carried the glyph; see End-of-run summary.
End-of-run summary
The trailing block lists result counts and wall clock.
All setup work (org context load, test-runner permission grant, test
resolution, mock generation, deploy, and cache-key computation)
collapses into one Preparing test run umbrella spinner, resolving to
✓ Preparing test run Ns (or ✗ on failure) when prep finishes. The
--verbose (-v) flag expands the umbrella into one indented sub-row
per stage so cold-daemon investigations can see which stage dominated;
in default mode only the umbrella row renders. The ✓ Ran on org row
prints only on failure, above the details, so the failure summary lands
before the per-test output scrolls; on success the Summary block’s
Passed count and Wall row carry the same facts.
Per-method rows right-align duration to a per-block column sized to the
longest method name in that class, so duration sits beside the name
rather than at the top-level phase column. A dim-amber ↺ glyph trails
any row served from cache; when at least one row carries it, a dim
↺ cached legend line lands above the Summary block.
In an interactive terminal, every class prints its rows one at a time:
classes served from cache cascade quickly, classes that ran on the org
fill in at the slower pace of their results landing. The Summary block
waits for the whole cascade to finish, so it always prints beneath the
last row rather than racing ahead of it. Non-interactive runs (piped
output, CI, --plain, --json) skip the animation and print each
block as soon as it is ready.
Success:
✓ Preparing test run 1.78s
PASS AccountTest
✓ rejectsBadInput 46ms ↺
✓ closeWon_uow 1.73s
Tests PASS 2 FAIL 0 SKIP 0
Wall 1.8s
Cache ↺ 1/2Under --verbose, the same run renders the umbrella plus its
children:
✓ Preparing test run 1.78s
✓ Load org context 180ms
✓ Ensure test-runner permissions 95ms
✓ Resolve tests 62ms
✓ Computing cache keys 2/2 1.45s
PASS AccountTest
✓ rejectsBadInput 46ms ↺
✓ closeWon_uow 1.73sThe trailing block collapses passed / failed / skipped counts into a
single Tests row with plain foreground-color labels (PASS mint,
FAIL red, SKIP amber) rather than inverse-background chips. Each
label sits one space from its value (PASS 19); pairs are separated
by two spaces (PASS 19 FAIL 0 SKIP 19), matching the row key→value
gap. The Cache row collapses to ↺ hits/total with the ↺ glyph in
full-intensity amber, matching the dim-amber per-method ↺ markers
inline above. When the cache served at least 1s of measured execution
this run, the Wall row carries a dim (saved ~Xs) suffix.
The Wall value is measured end-to-end, from process spawn (anchored
at Date.now() - process.uptime() * 1000) to the moment the Summary
block renders: Bun/Node startup, module imports, Commander dispatch,
daemon connect, test execution, and summary build. It tracks time(1)
total closely, minus the post-render update-notice fetch and V8
teardown that time keeps counting after the result lands. The
Cache row’s (saved ~Xs) suffix uses the same scope, so the saved
estimate compares like-for-like against the sf-cli baseline.
The Cache row of the Summary block shows ↺ <hits>/<total>:
methods served from cache versus run on the org. The per-method ↺
glyphs inline above carry the per-test detail.
Failure:
FAIL AccountTest
✓ acceptsValidInput 12ms
✗ rejectsBadInput
System.AssertException: Assertion Failed: expected exception
Class.AccountTest.rejectsBadInput: line 42, column 1
✗ Ran on org 1 failed of 2 1.8s
Summary
Passed 1
Failed 1
Skipped 0
Wall 1.8s
Each ✗ row prints the failure’s error message indented underneath in
red, followed by the stack trace in gray. Skipped tests render as ⊘ <method> (skipped) with the reason indented in gray. There is no
trailing === Failed/Skipped Test Details section; the data is inline.
A non-zero exit caused by test failures alone does not print the A complete log of this run can be found in: ... footer; it appears only
when the CLI itself aborted (org auth, daemon crash, parser
error, etc.) and the per-run NDJSON log is the only place to look.
Plain-mode progress
In an interactive terminal under --plain, the command keeps the
ten-second stderr heartbeat active with aggregate test progress instead of
printing one line per method as soon as it finishes. Progress heartbeat
lines are padded with a blank line before and after so completed class
blocks stay readable:
RUNS OrdersServiceImplTest 2/6 ✓2 40.71s
[35s] 86% ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱▱▱▱▱ tests 86/100 suites 7/8 ✓86 36.44s / ETA ~5.93sUnder CI (CI env) or any other non-TTY context (piped stderr), these
aggregate snapshots are hidden by default so batch logs are not flooded
with a fresh progress block every ten seconds; the periodic heartbeat is
disabled entirely and the run prints a single Running tests... liveness
line at the start instead. Pass --progress to force the full snapshots on
in CI, or --no-progress to suppress them in an interactive terminal (see
Execution behavior options).
Either way, the per-class jest-style result blocks below still print.
When a class completes, plain mode emits the jest-style result block for that class, separated from surrounding progress by a blank line:
PASS TrialsTest
✓ getTrials 147ms ↺
✗ terminate
System.AssertException: 1 != 2
Class.TrialsTest.terminate: line 42, column 1
⊘ unsupportedApi (skipped)✓ is a pass (with the test’s reported duration), ✗ is a failure
(with error and stack lines indented underneath), ⊘ is a skip, and ↺
marks a cache hit. The lines are indented two cells under their class
header. The --json mode
suppresses these lines so the stdout payload stays clean; --progress-events
takes precedence and emits NDJSON instead of pretty lines for programmatic
consumers.
Core options
| Option | Description | Example |
|---|---|---|
-f, --file <path> | Run tests for one Apex test class file. | -f path/AccountTest.cls |
-d, --dir <path> | Run tests discovered recursively in a directory. | -d src/tests |
-p, --pattern <glob> | Discovery pattern in dir mode (**/*Test.cls default). | -p '**/*Test.cls' |
-n, --class-names <names...> | Org-mode class execution. | -n AccountTest BillingTest |
-t, --tests <tests...> | Org-mode method execution with Class.method. | -t AccountTest.testHappy |
-m, --method <name> | Run a single method (file mode only). | -m testHappyPath |
-o, --target-org <alias> | Explicit org alias. | -o my-org |
--org | Resolve the default test list from the target org’s ApexClass index instead of the workspace. Only meaningful when no --file / --dir / --class-names / --tests is given. Default (flag absent) walks the workspace via the apex-ls daemon bridge and feeds the resolved Class.method list to the runner as if --tests were passed. | --org |
Execution behavior options
| Option | Description | Example |
|---|---|---|
--json | Emit the test result as a single JSON document on stdout, in the same shape as sf apex test run --json ({status, result: {summary, tests}, warnings}). Drop-in compatible with tools that already parse Salesforce CLI test output: Outcome is Pass/Fail/Skip, tests[].FullName is <ClassName>.<methodName>, time fields are formatted strings (e.g. "1500 ms"). Org-side identifiers latdx cannot produce (Id, QueueItemId, AsyncApexJobId, ApexClass.Id, testRunId) are emitted as empty strings, matching sf’s behavior on local-only runs. result.coverage is not populated. Implies --plain. | --json |
--progress-events | Emit newline-delimited JSON progress events to stderr (programmatic consumers). Events: {event:"queued"|"start"|"complete", className, methodName, status?, success?}. complete events always carry status and success (boolean) so consumers can branch on outcome without remapping status strings; failed complete events also include errorMessage? and message? so consumers can surface the failure reason. When a packed batch fails and the runner retries remaining tests, a queued event is re-emitted for each re-enqueued test; consumers should dedupe by className+methodName. | --progress-events |
--render-style <style> | Pick the live test renderer style. Values: compact (default; per-class live region: one row per in-flight class with completed/expected counter, rolled-up pass/fail/skip counts, and ticking elapsed timer; each class commits a jest-style PASS <Class> / FAIL <Class> block to stdout when its last method completes), sf (Salesforce CLI-style polling block: outcome / counts / pass-rate / elapsed under a === Test Run banner, no per-test stream), flight (three-region “flight strip”: header counters, scrolling completion log with ok/FAIL/skip tags inline, and a running list with per-test elapsed timer sorted by elapsed-desc). Override per-shell via LATDX_RENDER_STYLE env. All styles converge on the same final summary + failed/skipped detail block on stdout, so downstream tooling parses the same shape regardless of style. | --render-style flight |
--progress / --no-progress | Toggle the live test-progress display (the per-class RUNS rows, aggregate progress bar, and ETA heartbeat). The display is shown by default in an interactive terminal and hidden under CI / non-TTY. --progress forces it on (handy to watch a CI run live); --no-progress forces it off (quiet local runs). Neither flag affects the per-class PASS/FAIL result blocks, the final summary, --json, or --progress-events. | --progress |
-v, --verbose | Expand the Preparing test run umbrella into indented sub-rows for Load org context, Ensure test-runner permissions, Resolve tests, Generate mocks · adapt code, Deploy generated code, Computing cache keys. Also raises CLI logging to debug. | -v |
--no-cache | Disable the test result cache for this invocation. The engine runs every method without consulting or writing the on-disk store. Per-run only; the persistent kill is LATDX_CACHE_DISABLE=1. The cache is on by default for both engines (sf + latdx); see test-result cache below for the report line and JSON envelope key. | --no-cache |
--force | Refresh the test result cache. The cache wrapper stays active so this run re-executes every method (no reads), but the resulting TestResult rows still write back at the same input hash so the next run hits a warm cache. Distinct from --no-cache, which is a full bypass (no read AND no write). Mutually exclusive with --no-cache; if both are passed, --no-cache wins since there’s no wrapper left to write through. | --force |
--affected | Restrict the run to test methods whose static reach intersects the Apex / metadata files changed in the current git delta. Walks git diff --diff-filter=ACMRT <base>...<head>, asks the workspace daemon for the impacted set via the test/impacted RPC, then narrows method-level via cache/staleTests (Phase 4 AST + reach closure hash probed against the org-scoped result cache, mirroring latdx test impact watch --mode=hash). The bridge stays warm in the daemon so the CLI never spawns its own JVM; the narrow step is skipped when --no-cache or --force is set, or no default org is resolvable. When narrowing finds 0 stale candidates, the full reach set still passes through, so the runner replays cache hits with the standard PASS rows and summary (no bespoke “all fresh” message). Mutually exclusive with --file/--dir/--class-names/--tests/--method. Exits 0 with a regular Summary block (Changed, Tests, Result) when the delta is empty or reaches no tests, and no tests are executed and no warning prefix is emitted. Known false-negative kinds (triggers, flows, labels, custom-metadata, validation rules, permission sets, profiles, record types, sharing rules, workflows) are surfaced by test impact --base warnings; run the full suite when those metadata kinds change. | --affected |
--base <ref> | Git base ref consumed by --affected (compared as <base>...<head>). Default: main locally, or auto-detected from CI env on PR/MR pipelines. Detection order: GitHub Actions (GITHUB_EVENT_NAME=pull_request + GITHUB_BASE_REF), GitLab CI (CI_PIPELINE_SOURCE=merge_request_event + CI_MERGE_REQUEST_TARGET_BRANCH_NAME), Bitbucket Pipelines (BITBUCKET_PR_DESTINATION_BRANCH), Azure DevOps (BUILD_REASON=PullRequest + SYSTEM_PULLREQUEST_TARGETBRANCH, refs/heads/ prefix stripped), Jenkins multibranch (CHANGE_ID + CHANGE_TARGET). Resolved value is always origin/<branch> so the diff works against the remote-tracking ref a shallow clone fetches. CircleCI is not auto-detected (the CIRCLE_PULL_REQUEST env is a URL, not a branch). The workflow must still fetch the target ref (e.g. GitHub Actions fetch-depth: 0, GitLab GIT_DEPTH: 0, Bitbucket clone.depth: full). Ignored when --affected is not set. | --base origin/main |
--head <ref> | Git head ref consumed by --affected. Default: HEAD. Ignored when --affected is not set. | --head feature/x |
--failed-only | Suppress passed-test rows from the plain result block and the post-run detail block. Failed rows + the totals row remain identical to the default path. The live (TTY) per-class commit region is unaffected; the live UX intentionally still shows passes as they complete. --json output is unaffected; machine consumers always get the full result payload. | --failed-only |
--cancel-queued | Force-cancel every pre-existing ApexTestQueueItem row on the target org before enqueueing this run. Use when prior queued or processing tests are zombies you want to clobber unconditionally. Without this flag the engine runs a smart pre-flight that cancels only rows you own or rows older than 10 minutes and refuses to clobber fresh rows owned by other users (a Test queue conflict error is raised instead with the owner and age of each blocking row). | --cancel-queued |
--no-cancel | Disable the pre-flight cancel entirely. When a collision happens, the raw Test already enqueued <id> error from the Salesforce Apex Test API surfaces unchanged. Useful for diagnosing unexpected concurrent runs without auto-cleanup. | --no-cancel |
--dry-run | Resolve the full test selection (including --affected reach analysis and workspace discovery) and print it instead of executing. One entry per line on stdout: Class.method where methods are known, a bare class name when only the class is known. Ready to pipe into sf apex run test --tests or a RunSpecifiedTests deploy. Always reports the pre-cache selection (cache narrowing is skipped; --no-cache / --force change nothing). Exits 0, including when the selection is empty. See Dry run. | --affected --dry-run |
--classes-only | With --dry-run, print unique class names instead of Class.method lines. Long-only on test run (-n is --class-names here); errors without --dry-run. | --dry-run --classes-only |
--delimiter <string> | With --dry-run, join the selection into a single line with this delimiter instead of one entry per line, so a quoted "$(...)" substitution yields one shell token. Errors without --dry-run. | --dry-run --delimiter , |
Experimental anon-apex execution flags
The following flags drive the LATdx anon-apex (latdx-engine) execution path and are gated behind LATDX_EXPERIMENTAL=1. Without the enabler they are not registered and commander rejects them with error: unknown option '<flag>'.
| Option | Description | Example |
|---|---|---|
--sequential | Disable packed execution. | --sequential |
--concurrency <n> | Max concurrent batches. Default is auto-picked from the target org type: 20 for Developer Edition / trial / scratch orgs, 25 for production / sandbox orgs (see classifyOrgTier in @latdx/core). Pass an explicit number to override. | --concurrency 30 |
--coverage | Collect Apex code coverage. Off by default because SF reports coverage at class granularity (ApexCodeCoverageAggregate), which forces the cache to promote every method of a partially-cached class back to the miss list. Opt in only when the run needs coverage data. | --coverage |
Live method-by-method rendering is automatic: enabled when stdout is a TTY, --json is not set, CI is unset, and --plain was not passed. There is no --live / --no-live flag; use --plain (or pipe stdout) to force-off; rely on auto-detection otherwise. Pick the visual style with --render-style compact|sf|flight (default compact).
Run-mode knobs
Defaults: --engine sf + --db normal (the supported path). Anything else is experimental and gated behind LATDX_EXPERIMENTAL=1. The --engine and --db flags are not registered without the enabler (commander rejects them as unknown options); the corresponding env-var overrides also error with ExperimentalFeatureError when the enabler is unset.
| Flag / Variable | Default | Effect |
|---|---|---|
--engine <engine> | sf | sf (Salesforce Apex Test API via @salesforce/apex-node’s TestService; class-granular coverage). Experimental: latdx (LATdx anon-apex runtime). Override via LATDX_ENGINE env. |
--db <db> | normal | normal (org DB). Experimental: mocked (LATdx in-memory simulation). Override via LATDX_DB env. |
LATDX_EXPERIMENTAL | unset | Truthy (1 / true / yes / on, case-insensitive) opts in to experimental run-mode knobs and registers the experimental anon-apex execution flags above. |
LATDX_LATDB | unset | Alias: off / 0 / false maps to --db normal. Experimental otherwise. |
LATDX_BATCH_MODE | turbo | Experimental: eco maximizes tests per batch (fewer API calls, lower parallelism). |
LATDX_ANON_MODE | named | Experimental: inline inlines the test body into the ExecuteAnonymous payload (engine="latdx" only). |
When any value diverges from the default, the run header echoes the active value (e.g. Engine: latdx, DB: mocked) so it is obvious the run is non-standard.
Test-result cache
Every latdx test run against an org consults a per-org disk cache that identifies each result by (test class source + transitive dependency source + org schema revision). The cache layer is governed by ADR 0009 phase rules. Phase 0 is the safe baseline: results are cached but never reused, so the engine runs every method. Phase >= 1 unlocks per-rule skip logic, up to the Phase 4 default. See test-caching.md for the full rationale.
| Knob | Default | Effect |
|---|---|---|
--no-cache | off | Per-run override: skip the cache wrapper entirely for this invocation. Mutually exclusive with the run-summary cache line. |
LATDX_CACHE_DISABLE | unset | Persistent kill switch. Truthy (1, true, on, yes) skips every cache code path and surfaces cacheReport.killSwitch=true (no run-summary line is rendered). |
LATDX_CACHE_PHASE | 4 | Pin the active phase. 0 = safe baseline (every test runs, nothing reused); 1 = CLOSURE_EMPTY only; 2 = adds CLOSURE_INVARIANCE; 3 = adds SCHEMA_REV; 4 = adds per-method body / dynamic-pessimism / schema-closure (requires Java 11+ on PATH or JAVA_HOME). Out-of-range / unparseable values clamp to the default (4). |
LATDX_CACHE_RULE_<NAME> | unset | Per-rule disable. 0/false/off/no (case-insensitive) drops the named rule from the active set even when the active phase would otherwise include it. |
LATDX_CACHE_SHADOW_RATE | 0.01 | Shadow-validation sample rate in [0, 1]. On runs that already execute some tests, this fraction of cache hits is re-run on the org and compared against the cached result. Only fires when a run is already spawned (never on a full cache hit). Set 0 to disable; out-of-range / unparseable values fall back to the default. |
When the cache wraps the engine, the run prints a trailing line on stdout. Phase 0 includes a would have hit telemetry tail; Phase >= 1 includes the active phase:
Cache: 0 hit / 18 miss / 0 stale (18 written) [phase 0, would have hit 12]
Cache: 12 hit / 3 miss / 1 stale (4 written) [phase 2]{
"status": 0,
"result": {
"summary": {
/* unchanged sf-shape */
},
"tests": [
/* unchanged sf-shape */
],
"cacheReport": {
"hits": 12,
"misses": 3,
"stale": 1,
"writtenOnExit": 4,
"phase": 2,
"killSwitch": false,
},
},
"warnings": [],
}When the cache is disabled (--no-cache or LATDX_CACHE_DISABLE=1) the line is suppressed; the run header echoes Cache: off to make the opt-out visible. When the active phase is non-zero the header echoes Cache: phase N. Coverage rule for the SF engine: by default (coverage off) per-method skip is allowed; with --coverage on, any miss in a class promotes that class’s hits back to misses so coverage computes in one transaction.
Cache env vars are read per CLI invocation and forwarded to the daemon with the test-run request, so no daemon restart is needed for LATDX_CACHE_PHASE=2 latdx test run ... or a per-rule flag to apply to that run.
When shadow validation samples a hit on a run that already executed tests, cacheReport.shadowSamples and cacheReport.shadowMismatches are included. A non-zero shadowMismatches means a cached result disagreed with a fresh run; the fresh result is used for that method. Tune the sample with LATDX_CACHE_SHADOW_RATE (0 disables it).
Dry run
--dry-run resolves the full test selection a real run would execute (the same workspace discovery and --affected reach analysis) and prints it without running anything, so another tool can consume LATdx’s selection:
# Method-level list, one entry per line
latdx test run --affected --dry-run
# Unique class names, e.g. for a RunSpecifiedTests deploy
latdx test run --affected --dry-run --classes-only
# Pipe straight into the Salesforce CLI: xargs appends each line after
# --tests, and an empty selection runs nothing (pass -r to GNU xargs)
latdx test run --affected --dry-run | xargs sf apex run test --tests
# Command substitution works too: sf accepts space-separated --tests values
sf apex run test --tests $(latdx test run --affected --dry-run)
# Or a single comma-joined token (quoted, shellcheck-friendly)
sf apex run test --tests "$(latdx test run --affected --dry-run --delimiter ,)"Stdout carries only the selection; status lines stay on stderr. Entries are Class.method where methods are known, or a bare class name when only the class is known (for example a --class-names entry that exists only in the org). Both forms are valid sf apex run test --tests values. The list is always the pre-cache selection: an external runner has no access to LATdx’s result cache, so cached-fresh tests are never dropped and --no-cache / --force make no difference. Workspace and --affected dry runs make no org calls; only --org enumeration queries the org. An empty selection prints nothing and exits 0.
With --json, a single document replaces the line output:
{
"dryRun": true,
"selection": "affected (origin/main...HEAD)",
"mode": "org", // "org" or "file", mirroring the run mode
"tests": ["AccountServiceTest.testHappy", "OrgOnlyClass"],
"classes": ["AccountServiceTest", "OrgOnlyClass"],
"summary": { "totalEntries": 2, "totalClasses": 2, "classLevelEntries": 1 },
}tests may mix Class.method and bare class entries (summary.classLevelEntries counts the latter); classes is always the unique class set. --classes-only and --delimiter change the plain lines only, never the JSON shape.
Validation rules
| Validation rule | Behavior |
|---|---|
Cannot combine --class-names or --tests with --file or --dir | Validation error; pick org mode or local mode. |
Cannot combine --file with --dir | Validation error; pick a single source. |
--method cannot be used with --dir or org mode | Validation error; --method requires --file. |
--classes-only requires --dry-run | Validation error; it only shapes dry-run output. |
--delimiter requires --dry-run | Validation error; it only shapes dry-run output. |
test list
| Option | Description | Example |
|---|---|---|
-f, --file <path> | File mode: list methods in one Apex test class file. | -f path/AccountTest.cls |
-d, --dir <path> | Directory mode: list methods discovered recursively. | -d src/tests |
-p, --pattern <glob> | Discovery pattern in dir mode. | -p '**/*Test.cls' |
-o, --target-org <alias> | Org mode (resolves via flag or default org). | -o my-org |
-n, --classes-only | List unique class names only (omit method names). | -n |
--org | List tests from the target org’s ApexClass.SymbolTable instead of the workspace. Only meaningful when no --file/--dir is given. Flag absent (default) walks the workspace via the apex-ls daemon bridge (auto-spawns the daemon, requires LATDX_APEX_LS_JAR + LATDX_APEX_LS_BRIDGE_JAR). | --org |
--json | Structured output. | --json |
If --file/--dir is omitted, test list defaults to a workspace scan via apex-ls (no org call). Pass --org to fall back to the org-side ApexClass.SymbolTable scan (requires --target-org or a default org). When apex-ls is not configured on the daemon (Java missing or LATDX_APEX_LS_* jars unset), the command exits non-zero with a hint pointing at --org.
Output
Without --json, each discovered method is printed to stdout as a single fully qualified path: ClassName.methodName. A trailing summary line (Total: N file(s), M test method(s)) is also written to stdout. With --classes-only, each unique class name is printed once (sorted) instead of Class.method lines, and the summary becomes Total: N test class(es). Diagnostics from the logger remain on stderr:
latdx test list -d src/tests > tests.txt # data + summary captured, logs still on terminal
latdx test list -d src/tests | grep TestsAnti # filter Class.method paths
latdx test list -d src/tests 2>/dev/null # silence diagnosticsWhile the command runs, a progress spinner on stderr reports the current phase, elapsed time, the per-batch fetch / scan counts, and (during the scan phase) the running totals of test methods and test classes discovered so far. Example: [7s] Discovering tests in org my-org (scanned 1200/2500 classes; 837 test(s) across 71 class(es)). The spinner is suppressed in --json mode and in non-TTY contexts (CI=1, --plain, piped stderr) where a single static line is emitted per phase instead.
In org mode, test list queries ApexClass.SymbolTable (the compiler’s precomputed structural metadata) over the Tooling API instead of pulling each class body and parsing it locally. On a 5,000-class org this drops the cold-path runtime from ~2 minutes to ~10 seconds. Classes whose SymbolTable is null (compile errors or source the compiler does not expose) are skipped with an aggregate warning; use --dir against the local source tree if you need them included.
With --json, the structured payload is written to stdout instead. When combined with --classes-only, the payload shape is { classes: string[], summary: { totalClasses } } rather than the per-file methods list.
test impact
Show which test methods statically reach a set of changed Apex classes (no execution; uses Phase 4 reach analysis). Spawns its own apex-ls bridge; no daemon required.
| Option | Description | Example |
|---|---|---|
[identifiers…] | Positional class names (UsersSelector) or paths to .cls files (basename minus extension is taken as the class name). | Foo Bar |
-w, --watch | Continuous mode: watch the workspace and reprint on .cls / objects/** / sfdx-project.json / .forceignore changes (uses native FSEvents on macOS / inotify on linux; no filesystem polling). Save bursts coalesce to the next event-loop tick; concurrent flushes are serialized so an in-flight reprint queues exactly one follow-up. | --watch |
--mode <mode> | Watch-mode answer: hash (default; whitespace-invariant cache-key diff) or reach (cheap static reach). | --mode reach |
--base <ref> | CI mode: resolve changed .cls / .trigger via git diff --name-only --diff-filter=ACMRT <base>...<head> and feed the basenames into the impact resolver. | --base origin/main |
--head <ref> | Override the head ref for --base (defaults to HEAD). | --head HEAD~1 |
-v, --verbose | Print every affected test (default truncates to 50). | --verbose |
--truncate <n> | Truncate after N entries (ignored with --verbose). Defaults to 50; non-numeric values fall back to the default. | --truncate 100 |
--json | Emit JSON { changed, affected, truncated, triggerChange?, flowChange?, labelChange?, customMetadataChange?, validationRuleChange?, permissionSetChange?, profileChange?, recordTypeChange?, sharingRuleChange?, workflowChange? } instead of human output. | --json |
--base is one-shot only (incompatible with --watch) and folds together with positional identifiers when both are supplied. Three-dot range matches what GitHub renders for a PR (diff against the merge base). Deletions are dropped because the workspace no longer contains the file for apex-ls to index.
Watch-mode startup prints one progress line per phase (apex-ls bridge spawn, test-method discovery, reach indexing, Phase 4 baselining) on stderr with the wall-clock elapsed for that phase appended as (Ns). The elapsed counter measures from the first start of the phase, not the most recent progress tick, so long-running phases report their full duration.
Per-save flushes use the apex-ls bridge’s incremental refresh(path…) command to reparse only the touched files instead of reopening the workspace, so a single .cls save typically reprints in ~100-300 ms on a medium workspace. Schema, manifest, and deletion events still go through the full reload rebuild because incremental refresh cannot express those.
Class-only flushes skip the apex-ls bridge round-trip entirely when every edited class is non-test AND statically unreached by every indexed test method. The downstream pass still prints the 0 invalidated / 0 affected line; only the workspace rebuild is short-circuited. Schema, manifest, and test-class edits always take the full path because they can mint new reach edges.
When the resolved delta contains any Apex trigger (*.trigger / *.trigger-meta.xml) the JSON output includes triggerChange: true and human mode prints a stderr warning. Apex triggers fire on DML and have no static call-graph edge from any test method to the trigger. The affected list is therefore incomplete for trigger-only changes; run the full suite when the flag is set.
The same applies to Apex Flows (*.flow-meta.xml): record-triggered Flows fire on DML and auto-launched Flows are dispatched by name string at runtime via Flow.Interview.createInterview. When the delta contains any flow, the JSON output includes flowChange: true and human mode prints a stderr warning.
Custom Labels (*.labels-meta.xml) hit the same blast-radius problem: System.Label.X is a static-name reference, but the label VALUE (the only thing that affects test outcomes) lives only in the labels XML and is invisible to the bridge’s call graph. When the delta contains any labels file, the JSON output includes labelChange: true and human mode prints a stderr warning.
Custom Metadata Type records (*.md-meta.xml) follow the same pattern: Apex queries records via SOQL or Type.getInstance('Name') / getAll(), but the record VALUES live only in customMetadata/...md-meta.xml files. When the delta contains any custom-metadata record file, the JSON output includes customMetadataChange: true and human mode prints a stderr warning.
Validation Rules (*.validationRule-meta.xml) live under objects/<X>/validationRules/ and fire on DML; the bridge’s schema scan reads field / object metadata only, not rule bodies. When the delta contains any validation-rule file, the JSON output includes validationRuleChange: true and human mode prints a stderr warning.
Permission Sets (*.permissionset-meta.xml) and Profiles (*.profile-meta.xml) define field-level security and object permissions; tests using runAs(user) see different visibility (and different Schema.SObjectType.X.fields.Y.isAccessible() results) when those XMLs change. When the delta contains either kind, the JSON output includes permissionSetChange: true / profileChange: true (independently) and human mode prints a single combined stderr warning.
Record Types (*.recordType-meta.xml) live under objects/<X>/recordTypes/ and define picklist value sets, default field values, and activation. Tests using [SELECT Id FROM RecordType WHERE DeveloperName='X'] lookups or relying on default field values for specific record types see different behaviour when the XML changes. When the delta contains any record-type file, the JSON output includes recordTypeChange: true and human mode prints a stderr warning.
Sharing Rules (*.sharingRules-meta.xml) live in sharingRules/<ObjectName>.sharingRules-meta.xml (sibling to objects/) and define record-level access beyond the org-wide sharing default. Tests using runAs(user) see different record visibility (and SOQL results) when sharing rules change. When the delta contains any sharing-rules file, the JSON output includes sharingRuleChange: true and human mode prints a stderr warning.
Workflow rules (*.workflow-meta.xml) live in workflows/<ObjectName>.workflow-meta.xml and bundle workflow rules / field updates / outbound messages / tasks that fire on DML; same false-PASS surface as triggers and flows. Workflows are legacy (Salesforce migrates callers to Flows) but still active in many orgs. When the delta contains any workflow file, the JSON output includes workflowChange: true and human mode prints a stderr warning.
All ten flags are independent; any combination can fire on the same diff, and any one of them signals “fall back to the full suite”.
CI wiring (GitHub Actions)
IMPACT=$(latdx test impact --base origin/${{ github.base_ref }} --json)
NONREACH=$(jq -r '(.triggerChange // false) or (.flowChange // false) or (.labelChange // false) or (.customMetadataChange // false) or (.validationRuleChange // false) or (.permissionSetChange // false) or (.profileChange // false) or (.recordTypeChange // false) or (.sharingRuleChange // false) or (.workflowChange // false)' <<<"$IMPACT")
if [ "$NONREACH" = "true" ]; then
# Trigger / Flow / Label / CustomMetadata / ValidationRule / FLS / RecordType / SharingRule / Workflow blast radius cannot be statically resolved; run everything.
latdx test run
else
TESTS=$(jq -r '.affected | join(" ")' <<<"$IMPACT")
[ -z "$TESTS" ] || latdx test run --tests $TESTS
fiExperimental command gate
adapt, boost, clean, restore, and uninstall are gated behind LATDX_EXPERIMENTAL=1 (truthy: 1 / true / yes / on, case-insensitive). Without the enabler they are not registered: latdx --help omits them and latdx <cmd> exits non-zero with error: unknown command '<cmd>'. Set the env var to opt in. When enabled, each appears in the command list with an (experimental) suffix.
LATDX_EXPERIMENTAL=1 latdx adapt -f Foo.clsadapt
| Option | Description | Example |
|---|---|---|
-f, --file <path> | Required. Source Apex file to transform. | -f Foo.cls |
-o, --output <path> | Optional output destination. | -o out/Foo.cls |
-v, --verbose | Optional debug-level logging. | -v |
--json | Emit {outputPath,bytes} or {adapted} JSON. | --json |
Currently reads and writes the original content unchanged; the transformation pipeline is not wired into this command path.
boost
Adapt every .cls under a source directory for test acceleration (standard Salesforce Test API). Heavy lifting runs in BoostService from @latdx/core; the command is a thin wrapper.
| Option | Description | Example |
|---|---|---|
-d, --dir <path> | Required. Source directory containing .cls files. Walked recursively. | -d src |
-o, --output <path> | Required. Output directory for adapted files. Created if missing. | -o out |
-v, --verbose | Optional debug-level CLI logging. | -v |
--json | Emit {sourceDir,outputDir,transformedCount,unchangedCount,errors} JSON. | --json |
The terminal summary line reports transformed, unchanged, and errors counts; per-file error reasons are logged at warn/error level.
restore
Reverse boost-mode adaptations and restore original Apex classes from LATDX_ORIG markers. Operates on local files (dir mode), an org (org mode), or both in one invocation.
| Option | Description | Example |
|---|---|---|
-o, --target-org <alias> | Optional. Target org alias. Restores adapted classes in the org via the Tooling API. | -o my-org |
-d, --dir <path> | Optional. Source directory of local .cls files to restore in place. | -d src |
--dry-run | Optional. List adapted classes/files without writing anything. | --dry-run |
-v, --verbose | Optional debug-level CLI logging. | -v |
--json | Optional. Emit results as a JSON payload on stdout. | --json |
At least one of --target-org or --dir is required. When both are provided, dir restore runs first and the org restore runs after. Org-side errors exit non-zero.
validate
Deploy your changes to a sandbox or scratch org and run the tests they affect, in one command. Drop it into the CI job where you previously ran sf project deploy validate followed by a test run.
What it does, in order:
- Computes the components changed between
--baseand--head(same git range aslatdx test run --affected) and deploys them withsf project deploy start. With--fullit deploys every package directory instead. - Runs the tests affected by those changes through the standard
latdx test run --affectedpipeline (cached and accelerated). - Optionally checks per-class coverage of the changed Apex classes against
--coverage-thresholdand fails when any class is below it.
| Option | Description | Example |
|---|---|---|
-o, --target-org <alias> | Target org alias (uses default if not specified). | -o my-sandbox |
--full | Deploy every package directory instead of only the changed components. Tests still run for the changed components only. | --full |
--base <ref> | Git base ref for the delta (compared as <base>...<head>). Default: main locally; auto-detected on CI PR/MR pipelines. | --base origin/main |
--head <ref> | Git head ref for the delta (default HEAD). | --head HEAD |
--coverage-threshold <pct> | After the test run, fail (exit 12) when any changed Apex class is below this coverage percentage on the org. Forces re-execution of cache-fresh tests so the org’s coverage numbers reflect this run. Off by default. | --coverage-threshold 75 |
--dry-run | Print the paths that would deploy and the tests that would run, without deploying or executing anything. | --dry-run |
-v, --verbose | Optional debug-level CLI logging. | -v |
--json | Machine-readable output: one JSON line per phase (deploy, coverage) plus the standard test-run payload. | --json |
Exit codes: 0 pass · 10 deploy failed · 11 tests failed · 12 coverage below threshold · 64 bad usage.
# CI on a pull request: deploy the PR's changes and run affected tests
latdx validate -o ci-sandbox
# Full deploy, affected tests, 75% coverage gate on changed classes
latdx validate -o ci-sandbox --full --coverage-threshold 75Deleted metadata is not deployed (no destructive changes). When the git delta contains deletions, latdx validate warns and skips them; remove the components from the org separately. Production deployments stay with sf project deploy — tests inside a production validate run server-side, where coverage is enforced by Salesforce itself.
config
Manage CLI configuration. Subcommands:
| Subcommand | Description | Example |
|---|---|---|
latdx config set <key> <value> | Set a configuration value. | latdx config set channel latest |
latdx config get <key> | Read a configuration value. | latdx config get channel |
latdx config list | Print all configuration entries. | latdx config list |
All three accept --json for structured output: {key,value} for set, {key,value,source} for get, {values:[{key,value,source}]} for list (source is user or default).
Supported config keys:
| Key | Values | Default | Description |
|---|---|---|---|
channel | stable, latest | stable | Release channel for latdx upgrade and the background update check. |
autoUpdate | on, off | on | Silent background self-update after a CLI run, and about once an hour while a workspace daemon is running. CI is always skipped. Env LATDX_AUTO_UPDATE overrides (see installation.md). |
latdxBaseUrl | URL | https://latdx.com | LATdx site base URL used for browser auth and live license checks. Staging installers set this automatically; LATDX_BASE_URL overrides it for one shell/session. |
telemetry | on, off | on | Send anonymous usage metrics. Opt out with latdx config set telemetry off, or per-shell with LATDX_TELEMETRY=0 / DO_NOT_TRACK=1. See Telemetry. |
Telemetry
LATdx sends anonymous usage metrics (which commands run, how many tests a run executed, pass/fail counts, cache hit rate and time saved, plus environment basics like OS, CLI version, and whether the run was in CI) to help prioritize the product. It is on by default and never blocks or slows a command: if the network is down, telemetry is dropped silently.
What is never sent: source code, Apex class or method names, org names or ids, file paths, or email addresses.
Opt out at any time, by precedence:
LATDX_TELEMETRY=0(also acceptsfalse,off,no) for one shell/session.DO_NOT_TRACK=1, the cross-tool standard.latdx config set telemetry off, persisted in~/.latdx/config.json.
upgrade
Check for and apply CLI updates. latdx update is registered as an alias and accepts the same flags and subcommands.
| Subcommand | Description | Example |
|---|---|---|
latdx upgrade | Download and install the latest CLI version for the configured channel. | latdx upgrade |
latdx upgrade check | Check whether a newer CLI version is available without installing. Supports --json for machine-readable output. | latdx upgrade check --json |
Both subcommands respect the configured channel to decide which releases are eligible: stable considers published non-prerelease builds only, while latest is a superset that also accepts prereleases (so it is never older than the newest stable). Either way the highest eligible version wins, regardless of the order GitHub published the releases. Pass --latest or --stable to override the configured channel for a single invocation (they are mutually exclusive); the configured channel is left unchanged. To switch channels persistently use latdx config set channel <stable|latest>.
latdx upgrade --json emits {currentVersion,latestVersion,upgraded} after the install script finishes; install-script chatter is routed to stderr in JSON mode so stdout stays a single JSON document. latdx upgrade check --json emits {currentVersion,latestVersion,updateAvailable}.
Automatic pre-test FLS/OLS grant
Fresh scratch orgs and many sandboxes ship standard fields (for example Account.AccountNumber, Account.Rating, Account.Site) with Field-Level-Security unset for every profile including System Administrator. Deployed @IsTest classes bypass FLS on their own field references, so Salesforce’s native test runner succeeds without a grant. LATdx test execution runs through anon Apex, which enforces FLS at compile time, so without a grant test runs fail with Field does not exist on those same references.
To keep latdx test run working without manual setup, @latdx/core deploys and assigns a latdx_TestRunnerAccess permission set to the currently authenticated user before dispatching tests. The permset grants full object permissions plus readable=true editable=true FLS on every settable field the Tooling API exposes. The grant is cached two ways:
- In-memory on the
LatdxCoreinstance: subsequent test runs in the same process return instantly. - On disk at
~/.latdx/runner-access/<orgId>.json(one file per org), keyed by an inexpensive schema fingerprint (entity count + Account field count + latestCustomObject.LastModifiedDate). When the fingerprint matches and the user is still assigned, the cold path takes one Tooling query and one SOQL; when the fingerprint changes, LATdx regenerates the permset.
This grant deploys a permission set into the target org. To bypass the automatic grant (for example in tightly controlled sandboxes where the running user cannot deploy metadata, or when you manage the permset yourself), set LATDX_SKIP_TEST_RUNNER_ACCESS=1 in the environment before launching latdx test run. When the daemon is used, the opt-out is read when the daemon starts, so start a fresh daemon with the env var set:
latdx daemon stop
LATDX_SKIP_TEST_RUNNER_ACCESS=1 latdx test run -o my-orgCache and daemon commands (advanced)
Available unconditionally; intended for debugging and workspace cleanup. Size figures emitted by cache status and cache status --all-workspaces scale through B / KB / MB / GB / TB so multi-gigabyte caches render in their natural unit instead of inflated MB.
latdx cache status
latdx cache status --all-workspaces
latdx cache clear
latdx cache clear --all
latdx cache clear --temp # clear temp files only
latdx cache clear --daemon # kill the current workspace daemon only
latdx cache clear --all-workspaces
latdx cache clear --legacy
latdx cache clear -q # --quiet: suppress per-step output (scripts)
latdx daemon status
latdx daemon status --all-workspaces
latdx daemon status --in-flight # snapshot active phase frames in the current daemon
latdx daemon stop
latdx daemon stop --all-workspaces
latdx daemon restart
latdx daemon restart --all-workspacesWhen a new CLI binary is invoked against an already-running daemon, the
client asks the daemon to shut down so the next test run boots a process
that loaded the freshly-built code. The handover is bounded: the daemon
races its in-process shutdown against a 3000ms deadline, and the CLI
waits for the daemon PID to disappear before reconnecting. If a stuck
step would otherwise strand the old process holding the workspace
socket, the CLI escalates to SIGTERM and then SIGKILL on the daemon
PID. (The usual culprit is macOS uv__fsevents_close synchronously
blocking libuv inside chokidar teardown on huge workspaces.) You should
not need latdx cache clear --all to recover after a rebuild; if you
do, that is a bug.
All cache and daemon subcommands accept --json for machine-readable output:
latdx cache status --jsonemits{workspaces:[{workspaceId,cacheDir,cacheSize,cacheScanErrors,daemonRunning}]}.latdx cache clear --jsonemits{events:[…]}collecting the per-step messages the text mode prints.latdx daemon status --jsonemits{workspaces:[{workspaceId,running,pid,socketFile,logFile}]}.latdx daemon status --in-flight --jsonemits{snapshotAtMs,phases:[{name,startedAtMs,elapsedMs,depth}]}listing currently-openPhaseLoggerframes in the running daemon (emptyphasesmeans the daemon is idle). Only frames opened inside the daemon process itself are visible; if the work runs in a child process the daemon spawned, that process’s stack is not reported.latdx daemon stop --jsonandlatdx daemon restart --jsonemit{results:[{workspaceId,pid,outcome,error?}]}whereoutcomeis one ofstopped,already_stopped,exited, orfailed.
latdx cache status walks the workspace cache directory recursively to compute the size on disk. Entries that cannot be read (EACCES on a hardened subdir, ENOENT on a stat after the daemon’s mid-flight cleanup) are listed under a warning: failed to scan ... line on stderr; the reported size in that case is a lower bound, not silently shrunk.
LATDX_DEBUG namespace patterns
In addition to the boolean form (true / 1), LATDX_DEBUG accepts namespace patterns that selectively enable individual trace channels emitted through the debug package. Output goes to stderr in the standard <namespace> <message> +<delta> format.
| Value | Effect | Example |
|---|---|---|
LATDX_DEBUG=true (or 1) | Enable every latdx:* trace channel (legacy behavior). | LATDX_DEBUG=true latdx ... |
LATDX_DEBUG=* | Enable every namespace, including non-latdx: ones from third-party libraries that share the debug runtime. | LATDX_DEBUG=* latdx ... |
LATDX_DEBUG=cli:upgrade | Enable a single channel (auto-prefixed to latdx:cli:upgrade). | LATDX_DEBUG=cli:upgrade latdx ... |
LATDX_DEBUG=cli:* | Enable every channel under a subtree. | LATDX_DEBUG=cli:* latdx ... |
LATDX_DEBUG=core:cache,daemon:ipc | Enable several channels. | LATDX_DEBUG=core:cache,daemon:ipc latdx ... |
LATDX_DEBUG=cli:*,-cli:upgrade | Subtract one channel from a broader include. | LATDX_DEBUG=cli:*,-cli:upgrade latdx ... |
LATDX_DEBUG=false (or 0, unset) | Off. | LATDX_DEBUG=0 latdx ... |
Patterns without an explicit latdx: prefix are auto-prefixed so callers do not need to repeat the brand. The boolean form continues to drive the existing developer-only commands shown above plus the temp-file retention behavior in LATDX_KEEP_TEMP_FILES; namespace-pattern values do not change those behaviors and are intended for fine-grained trace gating only.
Each git worktree gets its own daemon process and cache namespace under ~/.latdx/workspaces/<id>/, so latdx cache clear and latdx daemon stop operate on the current worktree by default. Use --all-workspaces to reach across every worktree on the machine, and latdx cache clear --legacy to wipe stray ~/.latdx/cache and ~/.latdx/daemon.{sock,pid,log} files from earlier builds in one pass (user-global config.json and update-check.json are left alone).
Mode decision table
| Scenario | Daemon used? | Notes |
|---|---|---|
test run with org mode (--class-names, --tests, or no file/dir) | Yes | Auto-spawns daemon if needed; supports streaming callbacks. |
test run with file/dir mode | No | Runs directly through LatdxCore path. |
| Daemon connection/request fails | Fallback | CLI falls back to direct core execution for org-mode requests. |
Exit behavior
| Exit code | Meaning |
|---|---|
0 | Successful run/list/adapt path. |
1 | Validation/runtime/test failure path. |
Uninstall exit codes
latdx uninstall exits with code 1 when class restoration fails, matching the behavior of latdx restore.
Non-interactive / CI behavior
latdx test run auto-detects non-interactive environments (the CI env var set to any value except empty or an opt-out (false, 0, off, no), stdout not attached to a TTY, or --json passed). In those environments the CLI never blocks on a confirmation prompt:
- Debug log storage exceeded: when the scratch org hits its 1 GB
ApexLoglimit, the CLI automatically deletes all debug logs and retries the run instead of showing theDelete all logs to continue?prompt. Interactive terminals still see the prompt. --jsonmode: confirmation prompts always resolve to their default and emit anAuto-confirmed in non-interactive mode: <prompt>warning on stderr. This guarantees stdout stays a single parseable JSON document, even in an interactive TTY whereCIis unset.LATDX_AUTO_CONFIRM=trueadditionally forces auto-confirmation in interactive terminals (useful for local scripts).