Skip to main content
build turns your Python package into the flat, import-less namespace the platform’s sandbox executes, and writes it into the definition file’s code port in place. It is the compiler step of deploy, available on its own. It makes no network calls of any kind. It never resolves a blob, never lists definitions, and never resolves a credential — build runs in a checkout with no API key, no credentials file, and no connectivity. That is what makes it usable as a pre-commit hook and as the first, cheapest step in CI.

Synopsis

What it does

For each manifest entry that declares components[] — an entry with no bindings is skipped entirely, since there is nothing local to compile into it:
  1. Reads the entry’s source document. A missing file is MANIFEST_INVALID, hinting at pull.
  2. Resolves the entry’s category from the document’s own type, by the same rule every other command uses, so a manifest cannot build clean and then fail on the first command that talks to the platform.
  3. Binds each component by id or unique name, and for a code binding: checks the component’s language port is python, runs the remote-edit gate below, compiles entry_point under base_path, and writes the resulting content[] into the code port.
  4. For a value: {source} binding, reads that JSON/YAML file and writes it into the component’s value port.
  5. Writes the document back to source in its own format, and prints one line per definition:
Under --json the payload carries built, planned and dry_run alongside the envelope’s schema_version. There is no drift key here, unlike every other command in this group: build never lists the revision, so it has nothing to compare the manifest against.

Deterministic by construction

Compilation is a pure function of the sources. The compiled content is hashed (SHA-256 over its canonical JSON) and that hash becomes both the port’s content_hash and, through a uuid5 derivation, the message’s id. The writer touches no other key and reorders nothing. So a rebuild over unchanged sources leaves the definition file byte-identical, and git status stays clean. That is the property that makes the compiled code reviewable: a diff in the definition file means a real change in the package. One exception, once per port: created_at is carried forward from whatever is already in the file, and stamped with the current time only when there is nothing to carry. A code port the CLI has never built therefore changes on its first build and is stable from the second onward.

The remote-edit gate

Before overwriting a code port that already has content, build classifies it: --force skips the classification entirely. The other way out is blobhub workflow eject, which brings the edited code onto disk so you can reconcile it by hand first. A refusal stops the whole command, not just that definition. Definitions built earlier in the same run keep the content they were given — the file is written per definition, as each one finishes. The gate reads the code port in the local definition file, because that is the only document build has. An edit made in the visual editor reaches that file when you pull; diff is what reports the remote’s state without pulling. Run one of the two before a build you intend to deploy.

Imports and the allowlist

The compiler classifies every import as local, sandbox pre-bound, blob-allowlisted, or unavailable. The allowlist — a blob limit — needs an admin-scoped key to read, which build does not have and could not use offline anyway, so it reads the local cache under ~/.blobhub/cache/allowlist/, keyed on the manifest’s own blob: reference. With no cache entry, ALLOWLIST_UNREADABLE is raised as an advisory and an unresolved import degrades from the hard failure IMPORT_UNAVAILABLE to the advisory IMPORT_UNVERIFIED — the build proceeds and says it could not verify. --strict-imports promotes that back to a refusal, which is what a CI job should set. A cache entry older than seven days additionally raises ALLOWLIST_STALE. Two commands write that cache, both needing an admin key: deploy, which refreshes it live on every run, and blobhub blob limits --refresh. Everything that does not depend on the allowlist is enforced regardless: local module resolution, import cycles, symbol collisions, and names the sandbox pre-binds. The classification rules, the import-rewrite table and the compiler’s documented limitations are in the Compiler reference; what the allowlist extends — the pre-bound modules and names — is in the Sandbox reference.

--definition here is not the same check

Eight commands in this group take --definition; the seven other than build resolve it while loading the revision’s definition set, and reject an alias the manifest does not carry. build filters the manifest list directly, so an alias that matches nothing is a silent no-op: no error, no output, exit 0. Check the spelling against the manifest if a build reports nothing built.

--dry-run

Reads each document and resolves its category, then reports what it would build:
It does not compile, so a dry run cannot report a compile error, an unresolved import, or a remote edit. It answers which definitions are in scope, not whether they would succeed.

Errors

No credential or transport code can surface here, because no call is made. If build reports CREDENTIALS_NOT_FOUND, you are running a different command. Every code above, with its remediation, is in Error codes.

See also