Skip to main content
A logic.code component’s code is executed with its builtins replaced: the sandbox’s __builtins__ holds only __build_class__ and __name__, and everything else it offers is bound as an ordinary global before the code runs. There is no __import__, so any import statement that survives into deployed code raises ImportError: __import__ not found at execution time. That is the whole reason the compiler exists, and this page is its ground truth: what is already bound decides which imports it may drop, which names your modules must not redefine, and which imports it has to refuse.

What is pre-bound

16 standard-library modules, bound as module objects:
Typing: typing, Union, Literal. Exceptions: Exception, ValueError, IndexError, plus the six session exceptions — GraphMutationConflict, GraphTraversalLimitExceeded, InvalidGraphQuery, InvalidSessionObjectAlias, InvalidThreadEnvelope, InvalidGraphEnvelope. 59 built-in functions, re-exposed by name since __builtins__ no longer carries them:
Platform names, in six groups: Plus three deprecated aliases still bound for backward compatibility, and still a silent-collision hazard: post_event, get_input_value, set_output_value. Code Component documents what each of these does. This page is concerned only with the fact that they are bound.

What is not bound

sys, os, subprocess, requests, open, eval, exec. None of them can be reached by importing, either — there is no __import__. Code needing any of them belongs in an external worker rather than in a logic.code component.

Names your code must not redefine

Everything above is an ordinary global in one namespace, so a top-level definition in your code simply replaces it for everything that runs afterwards. That never raises. The platform does not read your globals back — it reads the objects it bound — so redefining set_result means your function is called instead of the platform’s, and redefining result means the platform still collects its own dict while your writes go nowhere. Either way the component completes and produces nothing, with no plausible cause anywhere in the trace. The compiler refuses this up front as SANDBOX_NAME_SHADOWED, over 109 reserved names: the 59 builtins, the platform names above, the typing names and the exception names. One gap worth knowing: the 16 stdlib module names are not in that reserved set. A module defining a top-level json — a function, a class, an assignment — compiles without complaint and shadows the pre-bound json module for every other inlined module in the same namespace. The related case that is caught is a local module file shadowing one, which the compiler resolves local-first and reports as the LOCAL_SHADOWS_SANDBOX_MODULE advisory.

Two gates before any of this runs

The platform checks both before executing a component:
  • content[] must be non-empty and content[0]["type"] must be "text".
  • The language port must resolve to python. An absent or empty port defaults to python platform-side; the CLI is stricter and refuses a code binding whose component has no language port at all, as LANGUAGE_NOT_PYTHON. Writing code into a component that cannot execute it is precisely the failure worth catching at build time.
The parts of content[] are then concatenated, each joined with a leading newline, and any part that is not type: "text" is skipped. So every part must be whole statements and never a partial line — which is why the compiler emits exactly one part per module.

Extending the set: workflow_component_code_imports

A blob limit listing the third-party modules a revision’s code may import. Each name is imported and merged into the execution namespace under its literal name from the list, and the compiler treats those names as droppable exactly like the pre-bound ones. Reading it is the CLI’s business as much as the platform’s, and it has three sharp edges. Reading it requires an admin key. The read and write keys the platform recommends for automation get a 403. So the compiler falls back to a local cache, and when it has none, 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 on build and deploy restores the refusal. A dotted entry binds a key that is not an identifier. numpy.linalg binds under the literal key "numpy.linalg", which no Python expression can name, so it is unreachable from sandboxed code. The CLI reports this as the ALLOWLIST_ENTRY_UNUSABLE advisory. It still has a use: importing it is what makes the submodule reachable as an attribute of an also-allowlisted parent. An entry colliding with a pre-bound name shadows it. The merge puts the allowlist last, so an entry named json or re replaces the stdlib binding for every component on the blob. The CLI reports this as ALLOWLIST_SHADOWS_SANDBOX. There is a fourth consequence the CLI cannot advise on, because it depends on the deployed environment rather than the list: the platform imports the whole list inside one try, so a name that fails to import aborts the loop and every module after it is missing too, with nothing said at execution time. Keeping the list short and verified is the only defence. The list is resolved when an execution is created and shipped with the queued message, so editing the limit does not affect executions already queued.

The local cache

build never talks to the platform, so it classifies against ~/.blobhub/cache/allowlist/, keyed on the manifest’s own blob: reference — lower-cased, and org-qualified exactly when the manifest itself qualifies it. A cache entry older than seven days raises the ALLOWLIST_STALE advisory; no entry at all raises ALLOWLIST_UNREADABLE. Two commands write that cache, both needing an admin key: deploy, which refreshes it live on every run, and blobhub blob limits --refresh, which is how an operator seeds it for builds running under a key that cannot read limits. Because the key is the reference as written, a manifest saying blob: checkout and a refresh run against acme-corp/checkout land on different cache entries. Qualifying the manifest’s blob: with its org avoids that — and avoids the collision between identically-named blobs in different orgs, which share one entry under a bare name. Every advisory named here, with its remediation, is in Error codes.

See also

  • Compiler — how these bindings decide what may be dropped, rewritten or refused.
  • blobhub blob limits — reading the allowlist, and the cache --refresh writes.
  • Code Component — what each bound helper does.
  • Concepts — the allowlist among the four platform behaviours that shape the binary.