> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blobhub.io/llms.txt
> Use this file to discover all available pages before exploring further.

# blobhub workflow eject

> Split a definition’s stored code into local files, to adopt a workflow authored in the browser

`eject` downloads a definition from the revision and writes its `logic.code` content back out as files on disk.
It exists for one situation: a workflow that was authored in the visual editor and now needs to live in a
repository.

**It is not the inverse of [`build`](/cli/workflow/commands/build).** The code stored on the revision is the
*compiled* code — the compiler rewrote every import into a flat-namespace rebinding (`import numpy as np` became
`np = numpy`) and that transform has no inverse. What `eject` recovers is whatever structure the compiler's
source markers preserved, never the import statements they replaced. The command prints that caveat on every run
that renders human output — it is part of the rendered form, so `--json` never carries it:

```text theme={null}
note: ejected code is the *compiled* source -- build rewrote every import into a flat-namespace rebinding, which cannot be recovered. This is an adoption starting point, not a round trip with build.
```

Treat the result as a starting point you finish by hand: restore the imports, split what wants splitting, then
`build` and [`diff`](/cli/workflow/commands/diff) until the compiled output matches what was there.

## Synopsis

```bash theme={null}
blobhub workflow eject -f <manifest> [--definition <alias>] [--output <dir>] [--force]
```

| Flag           | Default                                                | Description                               |
| -------------- | ------------------------------------------------------ | ----------------------------------------- |
| `--file`, `-f` | required                                               | Path to the workflow manifest.            |
| `--definition` | every definition in the manifest                       | Limit to this alias. Repeatable.          |
| `--output`     | a directory named after the alias, beside the manifest | Destination for an **unbound** component. |
| `--force`      | off                                                    | Overwrite files that already exist.       |

There is no `--dry-run`. `--force` is the guard instead: without it, a run that would overwrite anything writes
nothing at all.

## What it does

1. Resolves the blob and revision. This is a read, so a `commit`-phase revision is fine.
2. Lists the revision's definitions and matches them against the manifest.
3. For each manifest entry that has a remote counterpart, downloads the definition and, for each declared
   component binding, reads its `code` port content and splits it into files.
4. Checks **every** destination path for a conflict before writing **any** of them.
5. Writes the files, prints one line per file, and prints a manifest snippet for each component that has no
   binding yet.

```text theme={null}
ejected checkout_flow -> /home/ada/my-project/pkg/entry.py
ejected checkout_flow -> /home/ada/my-project/pkg/lib/pricing.py
```

A manifest entry whose alias is **not on the revision** is skipped in silence — there is nothing to eject — and
so is an entry that declares no `components[]` at all. `eject` only ever looks at bindings you have written
down, so adopting a component means naming it in the manifest first, even when it has no `code:` block yet.

Under `--json` the payload carries `definitions` — each with its `alias`, its destination `root` and the `files`
written — plus `bound`, alongside the envelope's `schema_version`. `bound` is `true` when the run printed no
manifest snippets: nothing needs adding to the manifest.

## Where the files land

The destination depends on whether the component is already bound:

| The binding          | Root                                    | `--output`                        |
| -------------------- | --------------------------------------- | --------------------------------- |
| Has a `code:` block  | That block's `base_path`                | **Rejected** — `MANIFEST_INVALID` |
| Has no `code:` block | `--output`, or `<manifest dir>/<alias>` | Chooses the root                  |

The rejection is deliberate. A bound component already has a root that `build` compiles from; accepting a second
one would put the package in two places and leave `build` compiling the one you stopped editing.

That split matches the two reasons to run this command. A **bound** component ejects into its own `base_path` —
this is the recovery path out of `REMOTE_EDIT` and `UNMANAGED_CODE`, where someone edited the code in the browser
and you need it on disk to reconcile by hand. An **unbound** component ejects into a fresh directory, because
creating that binding is what adoption *is*, and the command prints the snippet to paste:

```text theme={null}
add to the manifest for 'checkout_flow' to bind the ejected code:
  code:
    base_path: checkout_flow
    entry_point: entry.py
```

The suggested `entry_point` is the **last** file reconstructed, not the first. The compiler emits local modules
in dependency-first order and preserves that order into `content[]`, so the entry point — the module nothing else
in the package imports — is always last.

`--output` names one root for the whole run. Ejecting several unbound definitions into it at once is a mistake:
two definitions whose markers share a relative path would land on the same file.

## What the markers preserve

The compiler prefixes each module it emits with `# blobhub:source <relative path>`. `eject` reads those markers
back:

* Text under a marker becomes that marker's file, at that relative path, nested directories included.
* Text with **no** marker above it becomes one fallback file, named `<alias>_<name>.py` — where `<name>` is the
  binding's `name`, or the literal `code` when the binding names its component by `id`. This is the browser case:
  code typed into the visual editor has no markers at all, so a definition that was never built by this CLI
  ejects as exactly one file.
* A marker with an **empty body still produces an empty file.** An empty `__init__.py` compiles to its marker
  line and nothing else, and dropping it as "no content" would turn a package into a broken import.
* Non-text parts of `content[]` are ignored.

Nothing is discarded. Text before the first marker is written to the fallback file rather than folded into the
first marked one.

A marker path is text inside a document this CLI did not necessarily write, so it is untrusted: an absolute path,
an empty one, or one containing `..` is refused with `BASE_PATH_ESCAPE` rather than allowed to escape the
destination root.

A binding that names a component with **no `code` port** contributes no files and no error. If a definition
ejects nothing, check that the binding points at the component you meant.

## Nothing is written until every path is clear

`eject` collects every destination first and checks all of them for an existing file. If any exists and
`--force` was not passed, it raises `EJECT_TARGET_EXISTS`, naming every conflicting path, and **nothing is
written** — not the files that would not have conflicted, not a partial directory tree. The same holds for the
`--output`-on-a-bound-component refusal. A refused run leaves the filesystem exactly as it was.

With `--force`, existing files are overwritten. There is no backup; a checkout with uncommitted changes is the
only thing standing between `--force` and losing them.

## Errors

| Code                                                                                               | When                                                                                                                                                                                                                                                     |
| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MANIFEST_NOT_FOUND`, `MANIFEST_INVALID`, `MANIFEST_TYPE_MISMATCH`, `MANIFEST_VERSION_UNSUPPORTED` | The manifest is absent or unusable — including `--output` passed for a component that already has a `code:` binding.                                                                                                                                     |
| `DEFINITION_ALIAS_INVALID`, `DEFINITION_CATEGORY_UNKNOWN`, `DEFINITION_NOT_ACCESSIBLE`             | An alias fails the pattern; a category cannot be inferred or contradicts the document; a `--definition` alias is not in the manifest.                                                                                                                    |
| `BLOB_NOT_ACCESSIBLE`, `BLOB_NOT_WORKFLOW`, `REVISION_NOT_ACCESSIBLE`, `REVISION_NOT_WRITABLE`     | The target cannot be resolved, or its status is not `ready`. This is a read, so the phase may be `draft`, `commit` or `managed` — anything else raises `REVISION_NOT_WRITABLE`, which the shared resolver raises on the read path too, despite the name. |
| `COMPONENT_NOT_FOUND`, `COMPONENT_NAME_AMBIGUOUS`                                                  | A binding matches no component in the downloaded document, or matches more than one by `name`.                                                                                                                                                           |
| `EJECT_TARGET_EXISTS`                                                                              | A destination file already exists. Every conflicting path is named; nothing was written.                                                                                                                                                                 |
| `BASE_PATH_ESCAPE`                                                                                 | A `# blobhub:source` marker names an absolute or empty path, or one that climbs out of the destination root.                                                                                                                                             |
| `FORMAT_UNSUPPORTED`, `FORMAT_PARSE_ERROR`                                                         | A local `source` file has an unsupported extension, or does not parse — it is read to resolve the entry's category.                                                                                                                                      |
| `CREDENTIALS_NOT_FOUND`, `PROFILE_NOT_FOUND`, `INSECURE_CREDENTIALS_PERMISSIONS`                   | No usable credential, or a credentials file the CLI refuses to read.                                                                                                                                                                                     |
| `API_RATE_LIMITED`, `API_TRANSIENT_ERROR`, `API_NETWORK_ERROR`                                     | A call failed after its retries.                                                                                                                                                                                                                         |
| `DEFINITION_LIST_TRUNCATED`                                                                        | Advisory, on stderr. The exit code stays `0`.                                                                                                                                                                                                            |

Every code above, with its remediation, is in [Error codes](/cli/error-codes).

## See also

* [`blobhub workflow build`](/cli/workflow/commands/build) — the compile that produced the markers, and the gate
  whose refusals send you here.
* [`blobhub workflow pull`](/cli/workflow/commands/pull) — the definition *document*, verbatim, which is a
  different thing from its code.
* [`blobhub workflow diff`](/cli/workflow/commands/diff) — how you confirm the reconstructed package compiles
  back to what is deployed.
* [Component code](/blob-types/workflow/workflows/component-code) — the port `eject` reads.
