143 docs
Guides

Preview environments

Configure live previews so reviewers can inspect frontend and full-stack changes from a session.

A preview turns a session from "read the diff" into "try the changed app." It is most useful when reviewers need to click through UI, inspect full-stack behavior, or confirm that an agent's change works in context.

Previews are configured in .143/config.json under the top-level preview key. Commit that file to the repository so sessions, branch previews, and API-triggered previews all read the same contract.

How previews run

143 starts your app inside the session sandbox, then exposes the primary service on an isolated preview URL. Services share the sandbox filesystem and can call each other on localhost; browser traffic only reaches the configured primary service.

On startup, 143:

Reads the selected preview config.
Starts managed infrastructure when the config declares it.
Restores safe caches and runs preview.install when needed.
Starts preview services.
Waits for readiness before showing the preview as openable.

Session previews always use the session workspace as the source of truth. Caches can restore dependencies and build tool artifacts, but they never replace the agent's current source files.

Set up the config

Start with the smallest shape that can boot your app:

Create .143/config.json at the repository root.
Declare one service with a command, port, readiness path, and network policy.
Commit the file to the base branch.
Open a session and use Start Preview, or create a branch preview from the Previews page.

Use this single-service config for a frontend app with one dev server:

{
  "preview": {
    "name": "frontend",
    "command": ["npm", "run", "dev"],
    "cwd": "frontend",
    "port": 3000,
    "ready": { "http_path": "/", "timeout_seconds": 120 },
    "credentials": { "mode": "none" },
    "network": { "mode": "managed" }
  }
}

Notes:

  • command is argv, not a shell string. Use ["sh", "-c", "..."] when you need shell features.
  • cwd is relative to the repository root.
  • port is the port inside the sandbox. The public preview URL is generated by 143.
  • ready.http_path should return a 2xx or 3xx response when the app is ready.
  • Omit credentials, or set credentials: { "mode": "none" }, unless the app uses a legacy env-only credential set.
  • network: { "mode": "managed" } keeps sandbox egress on the platform-managed policy.

Full-stack preview config

Use services when the app needs a frontend and backend:

{
  "preview": {
    "name": "full-stack",
    "primary": "frontend",
    "services": {
      "frontend": {
        "command": ["npm", "run", "dev"],
        "cwd": "frontend",
        "port": 3000,
        "env": { "API_URL": "http://localhost:8080" },
        "ready": { "http_path": "/" }
      },
      "server": {
        "command": ["go", "run", "./cmd/server"],
        "port": 8080,
        "ready": { "http_path": "/healthz" }
      }
    },
    "credentials": { "mode": "none" },
    "network": { "mode": "managed" }
  }
}

Services share the same sandbox filesystem and localhost network namespace, so the frontend can call the backend at http://localhost:8080.

Keep previews current

When an agent changes code, a running dev server usually applies the update through its normal hot-reload path. 143 records that live update so the preview can stay current without a full restart.

Some changes need a restart because the process, dependency tree, or runtime contract changed. 143 marks the preview as needing refresh when changes touch files such as:

  • dependency manifests or lockfiles
  • .143/config.json or .143/preview-start.sh
  • build config, environment config, or database migrations

Refreshing a preview restarts the preview runtime with the latest workspace. Dependency, package-manager, and build caches are reused when their keys still match, so refreshes do not have to reinstall or rebuild everything from scratch.

Multiple preview configs

If a repository has more than one previewable app, use named configs:

{
  "preview": {
    "default": "web",
    "configs": {
      "web": {
        "name": "Web app",
        "command": ["npm", "run", "dev"],
        "cwd": "apps/web",
        "port": 3000,
        "ready": { "http_path": "/" },
        "credentials": { "mode": "none" },
        "network": { "mode": "managed" }
      },
      "docs": {
        "name": "Docs",
        "command": ["npm", "run", "dev"],
        "cwd": "apps/docs",
        "port": 3001,
        "ready": { "http_path": "/" },
        "credentials": { "mode": "none" },
        "network": { "mode": "managed" }
      }
    }
  }
}

The Previews page can auto-select the default config or let you choose a named config. For scripts, pass the config name with --config.

Add dependencies

Use preview.install when services need dependencies before they boot. Keep source-dependent builds out of install; put them in the service command so they run against the latest code.

{
  "preview": {
    "install": {
      "command": ["npm", "ci", "--no-audit", "--no-fund"],
      "lockfiles": ["package-lock.json"],
      "clean_paths": ["node_modules"],
      "verify_paths": ["node_modules/.bin/next"],
      "timeout_seconds": 420
    },
    "name": "frontend",
    "command": ["npm", "run", "dev"],
    "port": 3000,
    "ready": { "http_path": "/" },
    "credentials": { "mode": "none" },
    "network": { "mode": "managed" }
  }
}

143 reruns install.command when declared lockfiles or config change, or when a declared verify_paths entry is missing. With verify_paths, a fresh workspace can skip install after restoring a matching dependency cache.

For cache tuning, see Optimize preview startup.

Optimize preview startup

Use this section when previews boot correctly but cold starts are too slow.

Reuse dependency artifacts

Declaring verify_paths lets fresh workspaces skip install entirely. When a cache hit matches the exact install command, lockfile contents, and platform sandbox cache ABI, 143 restores the cached artifacts and skips install.command once every verify path exists. Without verify_paths, fresh workspaces run install.command.

Because the dependency cache key does not include the commit SHA, install.command must produce output that depends only on the install config and lockfiles. Do dependency installation here, not application builds. Source-dependent builds belong in the service command, where they run on every preview start.

Effective dependency cache paths are:

clean_paths + cache.paths + inferred paths from known dependency files
Dependency fileInferred cache path
package-lock.json, npm-shrinkwrap.json, pnpm-lock.yaml, yarn.lock, bun.lock, bun.lockbnode_modules
poetry.lock, uv.lock, Pipfile.lock, pdm.lock, requirements.txt, requirements-dev.txt.venv
go.mod, go.sumvendor

Inference is relative to the lockfile directory: package-lock.json infers node_modules, services/api/poetry.lock infers services/api/.venv, and go.mod infers vendor.

For Python projects, no cache object is needed when a listed lockfile uses a conventional local virtualenv:

{
  "preview": {
    "install": {
      "command": ["poetry", "install"],
      "lockfiles": ["services/api/poetry.lock"],
      "verify_paths": ["services/api/.venv"]
    }
  }
}

For Go projects that use vendoring, go.mod infers vendor:

{
  "preview": {
    "install": {
      "command": ["go", "mod", "vendor"],
      "lockfiles": ["go.mod", "go.sum"],
      "verify_paths": ["vendor"]
    }
  }
}

Add framework and package-manager caches

Add framework artifact caches with WorkDir-relative cache.paths, and add extra package-manager global caches with HomeDir-relative cache.package_manager.paths.

{
  "preview": {
    "install": {
      "command": ["npm", "ci"],
      "lockfiles": ["package-lock.json"],
      "clean_paths": ["node_modules"],
      "cache": {
        "paths": [".next/cache", ".turbo/cache"],
        "package_manager": {
          "paths": [".cache/custom-package-manager"]
        },
        "prewarm": { "enabled": true }
      }
    }
  }
}

Opt out when dependency artifacts are not safe to reuse:

{
  "preview": {
    "install": {
      "command": ["npm", "ci"],
      "lockfiles": ["package-lock.json"],
      "clean_paths": ["node_modules"],
      "cache": { "enabled": false }
    }
  }
}

Do not cache source directories, secret files, .git, or .143/cache/preview-install. If dependencies look stale, change the lockfile so the key changes or temporarily set cache.enabled: false.

Package-manager paths are relative to the sandbox home directory. Broad or sensitive paths such as ., .ssh, .gnupg, .codex, .claude, .config/gh, and .143 are rejected.

requirements.txt can be unsafe if dependencies are not pinned; use a lockfile or opt out. Mutable image tags such as latest can also leave stale dependency artifacts, so prefer immutable digests or versioned tags.

Add local infrastructure

Prefer platform-managed infrastructure for preview-only databases and caches. The credentials are generated per preview, injected as environment variables, and destroyed with the preview.

{
  "preview": {
    "name": "full-stack",
    "primary": "frontend",
    "services": {
      "frontend": {
        "command": ["npm", "run", "dev"],
        "cwd": "frontend",
        "port": 3000,
        "env": { "API_URL": "http://localhost:8080" },
        "ready": { "http_path": "/" }
      },
      "server": {
        "command": ["go", "run", "./cmd/server"],
        "port": 8080,
        "ready": { "http_path": "/healthz" }
      }
    },
    "infrastructure": {
      "db": {
        "template": "postgres-17",
        "inject_env": {
          "DATABASE_URL": "postgres://{{username}}:{{password}}@{{host}}:{{port}}/{{database}}?sslmode=disable"
        },
        "inject_into": ["server"]
      }
    },
    "credentials": { "mode": "none" },
    "network": { "mode": "managed" }
  }
}

Supported placeholders in inject_env are {{username}}, {{password}}, {{host}}, {{port}}, and {{database}}.

Secrets and config

Do not put API keys, database URLs, tokens, or generated config file contents in .143/config.json. Put non-secret values in preview.services.<service>.env; use preview.secrets when the preview needs admin-managed secret values.

For new configs, reference a named secret bundle. The repo declares only the bundle name, service scope, and non-secret hints about expected env vars or generated files:

{
  "preview": {
    "primary": "frontend",
    "services": {
      "frontend": {
        "command": ["npm", "run", "dev"],
        "port": 3000,
        "ready": { "http_path": "/" }
      },
      "server": {
        "command": ["go", "run", "./cmd/server"],
        "port": 8080,
        "ready": { "http_path": "/healthz" }
      }
    },
    "secrets": {
      "bundle": "repo-dev",
      "services": ["frontend", "server"],
      "env": ["DATABASE_URL"],
      "files": ["development.conf.json"]
    },
    "network": { "mode": "managed" }
  }
}

An org admin creates the repo-dev bundle outside the repo. The bundle can deliver multiple env vars and one generated file. For example, a file output can render a whole JSON document from one managed secret value:

{
  "type": "file",
  "path": "development.conf.json",
  "format": "json",
  "value": "secret:development_conf_json"
}

The value stored for development_conf_json should be the raw JSON document, including normal newlines and quotes. It does not need to be base64-encoded. 143 validates that the resolved value parses as JSON and writes the generated file into the preview workspace before services start.

Use format: "raw" for non-JSON file blobs such as PEM blocks or certificates. User-provided base64 is only needed when the application itself expects base64; it is not needed to protect JSON from shell quoting.

The older preview.credentials shape is still accepted for an env-var-only managed credential set:

{
  "preview": {
    "credentials": {
      "mode": "managed_env",
      "credential_set": "repo-staging",
      "env": ["DATABASE_URL", "STRIPE_SECRET_KEY"],
      "inject_into": ["server"]
    },
    "network": {
      "mode": "managed",
      "destinations": ["staging_db"]
    }
  }
}

In both models, the config only references admin-managed values by name and allowlists which outputs may be injected. Secret values are not committed to git.

Scope env secrets to the smallest service list with services or legacy inject_into. A frontend service usually should not receive server-only secrets because public frontend frameworks can expose environment variables in browser bundles. The file output is workspace-wide, so a bundle that generates a file must list every preview service.

Connected previews

Any preview with preview.secrets, preview.credentials.mode other than none, or preview.network.destinations is treated as connected. Connected previews pin launch behavior to the base branch so a session diff cannot change commands, ports, env handling, secret bundle names, or generated secret file paths while secrets are in scope.

Readiness

143 starts declared services and waits for their readiness probes. A preview becomes available only after the primary service is reachable.

143 reads .143/config.json from the repo.
It starts infrastructure, install steps, and services in dependency order.
Each service must pass its readiness probe.
The session page exposes the preview on an isolated preview origin.

Every service also receives platform-injected environment variables for preview-specific behavior.

VariableUse
HOSTDefaults to 0.0.0.0 so frameworks bind where the preview gateway can reach them.
ONEFORTYTHREE=trueAlways injected. Use this to detect that the process is running on 143. This name is reserved; preview configs and secret bundle env outputs that declare it fail validation.
ONEFORTYTHREE_ENV=previewAlways injected for preview runtimes. Use this to disable background consumers, schedulers, profilers, telemetry exporters, and other work that is useful in production but not needed to serve the preview. This name is reserved; preview configs and secret bundle env outputs that declare it fail validation.
PREVIEW_ORIGINPublic preview URL, when available. Use this as the external base URL for callbacks, redirects, and absolute links that must point back to the public preview URL.

Runtime environment

ONEFORTYTHREE=true and ONEFORTYTHREE_ENV=preview are the stable signals that app code is running inside a 143 preview. They are injected by the platform for every service. These names are reserved in configs and secret bundle env outputs, and declarations fail validation.

Use ONEFORTYTHREE_ENV=preview in startup code to keep the serving path enabled while skipping work that can exceed preview limits or make previews noisy:

  • background consumers
  • schedulers and cron loops
  • profilers and telemetry exporters
  • expensive startup warmers
  • non-serving maintenance processes

Use previews

Use previews from:

  • A session: click Start Preview after the sandbox is available.
  • A branch: go to Previews, choose repository and branch, then start a preview.
  • CI or scripts: create a scoped preview API token in Settings, then call the preview API or the bundled 143 preview create command with --repo, --branch, and optional --commit-sha.

Previews are most useful for UI changes, full-stack workflow changes, and review paths where the correct result is easier to see than infer from a diff.

Troubleshooting

SymptomWhat to check
No preview button or PREVIEW_NO_CONFIG.143/config.json is missing or has no preview section.
primary does not reference a servicepreview.primary must match a key in preview.services.
Readiness timeoutConfirm the service binds to 0.0.0.0, listens on the declared port, and returns 2xx or 3xx on ready.http_path.
Secret missing in a serviceCheck the secret bundle name, env allowlist, and services scope. For legacy credentials, check credential_set and inject_into.
Generated JSON config file is invalidStore the raw JSON document in the managed secret and use a file output with format: "json" and value: "secret:<name>". Do not base64-encode it unless the app expects base64.
Preview points at localhost in redirectsUse PREVIEW_ORIGIN for the app's external base URL.

References

On this page