> ## 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 scheduler pull

> Reverse each deployed schedule’s stored UUIDs back into a symbolic manifest, overwriting the file

`pull` reads every schedule on the revision and writes them back out as manifest entries — turning the revision
ids, session ids and org-qualified blob references the platform stores into the names a person wrote in the first
place.

It is how an existing scheduler blob becomes a file you can version-control, and how a schedule someone created
in the browser gets adopted into a manifest that already exists.

## Synopsis

```bash theme={null}
blobhub scheduler pull -f <manifest>
```

| Flag           | Default  | Description                     |
| -------------- | -------- | ------------------------------- |
| `--file`, `-f` | required | Path to the scheduler manifest. |

## It overwrites the manifest, with nothing to stop it

There is no `--force` and no `--dry-run`. The file named by `-f` is replaced with a freshly generated document
every time, and the run before it did not ask.

This is worth stating plainly because the file being replaced is **your own hand-authored manifest**, not a
generated artifact. Its comments, its key order, its quoting, and any entry it declares that the revision does not
actually carry are all gone afterwards. Against a revision holding no schedules at all, a two-entry manifest comes
back as:

```yaml theme={null}
type: scheduler_blob_deployment
version: '1.0'
blob: acme-corp/checkout-ops
revision: latest
schedules: []
```

That is the correct answer — the revision really does hold nothing — but it is also the whole file. The single
quotes are the writer's own: it emits YAML that round-trips, quoting `version` so it cannot be read back as a
float and an `invocation_time` so it cannot be read back as a timestamp, and leaving alone what does not need it.

The document is regenerated rather than merged, deliberately: the point of `pull` is to report what the platform
actually holds, and folding stale local entries back in would defeat it. It also means `pull` is not the command
to reach for when you want to know what would change. That is
[`diff`](/cli/scheduler/commands/diff), which writes nothing at all. Run it first, and keep the manifest in
version control so a surprising `pull` is one `git checkout` away.

Elsewhere in this CLI a local write is guarded: [`blobhub workflow eject`](/cli/workflow/commands/eject) refuses
outright with `EJECT_TARGET_EXISTS` if any destination file exists. `pull` does not, here or in the `workflow`
group — a pull's whole purpose is to replace the file, where an eject's is to create one.

## What it does

1. Loads the manifest and resolves the scheduler blob and revision. This is a read, so a `commit`-phase revision
   pulls fine.
2. Pages `list_schedules` to completion.
3. Sets aside every schedule the platform stored with **no alias** — they cannot be expressed as manifest entries.
4. Reverses each remaining schedule's stored target back into symbols.
5. Writes a fresh document to the manifest path, entries sorted by alias.

```text theme={null}
pulled daily_checkout_report -> schedules.yaml
pulled flash_sale_launch -> schedules.yaml
pulled weekly_cleanup -> schedules.yaml
skipped 1 schedule(s) with no alias -- they cannot be declared in a manifest and are still firing on their configured schedule: 419365d8-a798-49dd-ad29-5f97a9c1b901 (the removal path is 'blobhub scheduler prune', not yet available)
```

Under `--json` the payload carries `blob`, `schedules` (the aliases written), `unmanageable` (the ids skipped)
and `path`, alongside the envelope's `schema_version`.

## Reversal, field by field

A stored schedule records coordinates. A manifest declares names. Each of the four target fields is reversed by
its own rule:

| Stored                        | Written back as           | Rule                                                                                                                                                                                                            |
| ----------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The blob behind `revision_id` | `blob: checkout`          | A **bare name** when the target blob lives in the manifest's own org. A target in another org is qualified with that org's raw id, since a blob record carries no human-readable org handle to qualify it with. |
| `revision_id` itself          | `revision: latest`        | `latest` only while that revision genuinely still leads the blob. If the blob has moved on, the literal id is written, because `latest` would no longer mean the same revision.                                 |
| `session_id`                  | `session: scheduled_runs` | The session's alias when it has one, and the raw id when it does not.                                                                                                                                           |
| `workflow_alias`              | `workflow: checkout_flow` | Already an alias in storage — the platform never pins it to a definition id — so it passes through unchanged.                                                                                                   |

A pinned `target.revision` survives a pull. Nothing in a stored schedule records whether the manifest wrote
`latest` or a literal UUID — the platform keeps only the resolved id — so `pull` reads that intent back from the
**manifest entry it is refreshing**, matched by alias. A schedule the manifest never carried has no such intent to
recover, and comes back as `latest` when its target revision still leads and as a literal id when it does not.

The written entry always carries `type: workflow` in its target, even where the manifest you started from omitted
it. The two are equivalent: `workflow` is both the default and the only accepted value.

## It adopts drift

`pull` writes out **every** aliased schedule on the revision, not only the ones your manifest already names. A
schedule created in the web UI, or by a colleague's deploy, is drift before the pull and an ordinary manifest
entry after it.

That is the intended path from a browser-managed scheduler blob to a declarative one, and it is the only
resolution for drift that this group offers — the alternative, removal, needs `blobhub scheduler prune`, which is
not yet available. It is also the reason to read the version-control changes to your own manifest afterwards:
adopting drift is a decision, and `pull` makes it for every aliased schedule at once.

## Aliasless schedules are skipped, not silently

A schedule the platform stored with no alias has no manifest representation. `alias` is a schedule entry's
required identity, so writing one out would produce a file this CLI's own loader rejects — and inventing an alias
for it would make the next [`deploy`](/cli/scheduler/commands/deploy) create a second, separate schedule beside
the one already firing.

So it is left out of the document and named on its own output line instead, by id, under `unmanageable` in the
JSON payload. It is still firing; dropping it in silence is how an operator ends up with a schedule they cannot
see. To bring one under management, give it an alias through the platform's
[Update Schedule](/blob-types/scheduler/operations/update-schedule) operation and pull again.

## `pull` reads the target blobs too

Reversal is not a local transformation. For every schedule, `pull` reads the target revision, the target blob and
the target session — so it needs read access to every blob any schedule points at, not just to the scheduler blob
itself.

One unreachable target is enough to stop the whole command. Every entry is reversed before anything is written, so
a target that cannot be read aborts the pull with the platform's `403` surfaced as `AUTH_INVALID`, and the
manifest on disk is left exactly as it was. Use [`ls`](/cli/scheduler/commands/ls) when you only need to see what
the revision holds: it reads the scheduler blob and nothing else.

## `pull` and `deploy` are not inverses

Worth repeating here, because the file-in, file-out symmetry of `workflow pull`/`push` makes it tempting to assume
otherwise: a round trip through `pull` and [`deploy`](/cli/scheduler/commands/deploy) is not the identity.
`deploy` resolves symbols into UUIDs and **may create a session that did not exist**; nothing in a manifest
deletes a session, and `pull` will not undo it. See
[Scheduler overview](/cli/scheduler/overview#pull-and-deploy-are-not-a-symmetric-pair).

## Errors

| Code                                                                                               | When                                                                                                                                                                                                                                                                                                           |
| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MANIFEST_NOT_FOUND`, `MANIFEST_INVALID`, `MANIFEST_TYPE_MISMATCH`, `MANIFEST_VERSION_UNSUPPORTED` | The manifest is absent or unusable. It must load before it can be overwritten — `pull` reads its `blob`, `revision` and existing entries.                                                                                                                                                                      |
| `SCHEDULE_ALIAS_REQUIRED`, `SCHEDULE_ALIAS_INVALID`                                                | An existing manifest entry has no `alias`, or one that fails `^[a-z0-9_-]{6,42}$`. Checked at load, before the file is replaced.                                                                                                                                                                               |
| `BLOB_NOT_ACCESSIBLE`, `BLOB_NOT_WORKFLOW`, `REVISION_NOT_ACCESSIBLE`, `REVISION_NOT_WRITABLE`     | The scheduler blob cannot be resolved, is a blob of another domain, or its revision is unreachable or 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. |
| `AUTH_INVALID`                                                                                     | A target blob, revision or session behind a deployed schedule is unreachable with this key. Nothing is written.                                                                                                                                                                                                |
| `FORMAT_UNSUPPORTED`, `FORMAT_PARSE_ERROR`                                                         | The manifest has an unsupported extension, or does not parse.                                                                                                                                                                                                                                                  |
| `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.                                                                                                                                                                                                                                                                               |

A skipped aliasless schedule is not an error: it is reported and the exit code stays `0`.

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

## See also

* [`blobhub scheduler diff`](/cli/scheduler/commands/diff) — what a pull would overwrite, before it does.
* [`blobhub scheduler deploy`](/cli/scheduler/commands/deploy) — the other direction, and why it is not the
  inverse of this one.
* [Scheduler manifest](/cli/scheduler/manifest) — the document `pull` writes, field by field.
* [`blobhub workflow pull`](/cli/workflow/commands/pull) — the same verb in the group where it really does have a
  symmetric counterpart.
