Your tests pass. On your machine.

otherbox runs your test command once as it is, then once per environment that differs from yours by exactly one thing — a timezone, a locale, an empty home directory, a temp path with a space in it — and tells you which single change turned it red, with the line to reproduce it.

npm version MIT license zero dependencies
$ npx otherbox
otherbox — one thing different at a time
  command: npm test

  baseline    pass    6.1s    your environment, unchanged
  tz          FAIL    6.3s    a clock that is not yours
  locale      pass    6.0s    a language that is not yours
  home        pass    7.4s    a home directory with nothing in it
  color       pass    6.1s    a terminal that wants colour
  narrow      pass    6.0s    a narrow terminal
  ci          pass    6.2s    the other side of the CI branch
  clean-env   FAIL    6.0s    none of your shell
  spacey-tmp  pass    6.1s    a temp path with a space in it
  node        skip    0.0s    a different Node, if one is parked nearby

2 of 8 environments tested failed. Your suite passes here and would not pass there. 1 environment skipped: node.

The one machine whose agreement proves nothing

A suite is green on the machine that wrote it. That machine has your timezone, your locale, your ~/.gitconfig, your NODE_ENV, your eighty-column terminal and a /tmp with no spaces in it. Every one of those is an assumption your tests are quietly allowed to make, and none of them travels.

The usual way to find out is a red build on someone else's box, or on a runner, or in an issue — a failure with a dozen differences at once and no way to say which one did it. otherbox changes one thing per run, so a failure has exactly one suspect and the fix starts already narrowed.

It spawns your command

No plugin, no framework integration, no config file. npm test, pytest, go test ./..., make check — if a shell can run it, otherbox can run it nine more times.

It refuses to guess

If your suite is already red before anything changes, otherbox stops with exit 2 rather than perturbing a broken baseline and blaming a timezone for it.

Install

npx otherbox              # runs npm test in nine environments
npm i -D otherbox         # or keep it in the project

Requires Node 18.17+. Zero dependencies.

Use

otherbox                                  # default command: npm test
otherbox -- npm run test:unit             # any command, after --
otherbox --only tz,locale                 # just these
otherbox --skip home,clean-env            # all but these
otherbox --json                           # machine-readable, for CI
otherbox --repeat 3                       # each environment 3 times: flake or finding?
otherbox --why clean-env                  # what a pass there proves — and what it does not
otherbox --list                           # what each environment is and catches
otherbox --timeout 120                    # seconds per run (default 600)

Exit codes: 0 every environment passed · 1 at least one failed or was flaky · 2 the command was wrong, or your suite already failed — or failed only sometimes — before anything was changed.

The nine environments

Each one changes one thing. Nothing else moves.

idone thing differentwhat it catches
tzTZ=Pacific/Kiritimatidates formatted, parsed or compared in the machine's local time. UTC+14 is already tomorrow for most of your working day.
localeLC_ALL=tr_TR.UTF-8Intl formatting, localeCompare sort order, toLocaleUpperCase — Turkish is the classic (iİ).
homeHOME = a fresh empty directoryanything read out of your home that a new machine has not got: ~/.gitconfig, ~/.npmrc, credentials, a warm cache.
colorFORCE_COLOR=3, no NO_COLORoutput compared as plain text with ANSI escapes switched on — snapshots that match only because the pipe stripped the colour.
narrowCOLUMNS=40output wrapped, padded or truncated to the width of the window that ran it.
ciCI flipped to whichever it is notcode and tests that branch on CI — the branch you never take where you can see the output.
clean-enveverything but a small allowlist removedwhat your shell quietly hands the suite: NODE_ENV, npm_config_*, proxies, a token in your profile.
spacey-tmpTMPDIR = a path containing a spacepaths interpolated into shell commands without quotes; anything assuming a temp path is one word.
nodePATH prepended with a second Node's bin dir, if one is found nearbybehaviour that only holds on the exact Node version you happen to have installed. Skipped, honestly, if this machine has only one.

clean-env keeps only PATH, HOME, TMPDIR/TMP/TEMP, SHELL, USER, LOGNAME, PWD, TERM, LANG and the handful of variables Windows needs to start a process.

Reading a failure

A red environment prints what it changed, what that change is known to catch, the exact line to reproduce it, and the tail of your own output. No interpretation of your test — just the one variable and your words back.

tz — a clock that is not yours
  catches: dates formatted, parsed or compared in whatever timezone the
  machine happens to be in. Pacific/Kiritimati is UTC+14, so for most of your
  working day it is already tomorrow there.
  reproduce: TZ=Pacific/Kiritimati npm test
  last lines:
    AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
          code: 'ERR_ASSERTION',
      actual: '2026-08-17',
      expected: '2026-08-18',
      operator: 'strictEqual'

That is a real run: a date compared against toLocaleDateString, correct in every timezone west of the dateline and wrong in the one otherbox picked.

Flake or finding? --repeat

A single red environment has two explanations: the change broke your suite, or your suite is unreliable and this run is where the coin came up tails. --repeat <n> runs each environment n times and separates them.

$ otherbox --repeat 3 --only tz,clean-env
  baseline    pass    6.1s    your environment, unchanged
  tz          FAIL    6.3s    a clock that is not yours
  clean-env   flaky   6.0s    none of your shell

1 of 2 environments failed every run. Your suite passes here and would not pass there. 1
more was flaky — failed some runs, passed others — which says nothing about the change.

fails every run

A finding, and it says so: failed all 3 runs — consistent, not a flake.

fails some runs

Reported as flaky and kept out of failed in --json — it gets its own flaky array. The environment is not the suspect; your suite is.

And if the baseline itself fails some runs and passes others, otherbox stops with exit 2 before any environment runs. A command that cannot agree with itself twice cannot have a failure attributed to anything. The exit code still counts a flake as something to look at — the report just refuses to blame a timezone for it.

What a pass is worth --why

A green environment is a specific, narrow claim. --why <id> prints the claim and its limits, in the same words as What it can't tell you — so the terminal and the docs cannot drift apart.

$ otherbox --why tz
tz — a clock that is not yours

  changes: TZ=Pacific/Kiritimati

  A passing tz proves
    - your command passes with TZ set to a zone fourteen hours ahead of UTC,
      where the local date is already tomorrow for most of your working day
    - that nothing under test turns the process timezone into a different date,
      ordering or format that an assertion then rejects

  It cannot
    - prove the code is timezone-correct. One zone is one sample: a negative
      offset, a half-hour offset (Asia/Kolkata is +05:30) and a DST boundary are
      all unexercised — Kiritimati has no DST
    - see a timezone that comes from anywhere but TZ — a config file, a database
      session, a hard-coded "America/New_York", a browser
    - move the clock. Only the zone changed; the instant is still now, so
      nothing here tests a date in the past or the future

It answers in an empty directory

--why reads no files and starts no processes — no project, no command, no temp directory. You can read what a run would be worth before you have anything to run.

The limits are not optional

A test fails if any environment lacks a non-empty It cannot list. A tenth environment cannot ship without someone writing down what it fails to prove.

otherbox --why on its own lists the nine environments, --why all prints every one in full, and --why --json is the same content as data. An unknown id is refused by naming the real ones.

In CI

- run: npx otherbox --json > otherbox.json
  continue-on-error: true      # while you work through what it finds

Or gate on a subset you have already cleaned, and let the rest stay advisory until it is true:

- run: npx otherbox --only tz,clean-env

The JSON report names every environment, its exit code, its duration, the reproduce line and the tail of its output — enough to post a comment or fail a job on a specific id.

What it can't tell you

The same list is in the README, and it is deliberately the first thing you read there too.