Browse documentation
Product documentation · snapshot 2026-10-28 · View source ↗

These are the product’s technical docs at this revision. Historical version names and status notes are preserved; see Releases for download qualifications.

Read as Markdown

Guided and scripted project migration

On this page

hn migrate asks for the source, destination, explicit scope and mappings, shows the actual configured capabilities and findings, and saves an inspectable plan before asking for the exact answer APPLY. NO leaves the plan saved. Empty answers, an unterminated answer and EOF never approve a finding or an apply. hn migrate help lists the executable flags.

hn migrate plan is the same planning path with explicit arguments. It never applies, including when every decision is supplied. --guided can fill missing arguments and collect individual decisions. There is no blanket --yes flag. apply and resume use the same guarded engine and require --confirm-apply, or --guided followed by the exact APPLY answer.

Local archive without accounts

Run from any directory. The session's parent directory must already exist; the session itself must be absent. Keep sessions outside selected committed content, for example below the resolved Git directory in hn/migrations/NAME.

hn migrate capabilities
hn migrate plan --source-kind local --source-repository /work/project \
  --ref refs/heads/main \
  --destination-kind archive --destination-repository /backups/project.hn \
  --session /work/project/.git/hn/migrations/export
hn migrate inspect --session /work/project/.git/hn/migrations/export
hn migrate apply --session /work/project/.git/hn/migrations/export --confirm-apply
hn migrate inspect --archive /backups/project.hn

Create the private session parent deliberately before running that example. Repeat --ref for each full selected branch, tag or native actor ref. Select native actor histories under refs/hn/actors/ explicitly to retain those signed facts and their required dependency closure. Selected committed history stays exact; export is not a secret scrubber. Working files, unselected refs, private signing identities, ambient credentials and private migration journals are not exported.

Import to an existing destination Git repository:

hn migrate plan --source-kind archive --source-repository /backups/project.hn \
  --destination-kind local --destination-repository /work/new-project \
  --decision local/governance=preserve --session /private/import-plan
hn migrate inspect --session /private/import-plan
hn migrate apply --session /private/import-plan --confirm-apply
hn migrate imports --repository /work/new-project
hn migrate inspect --repository /work/new-project --snapshot sha256:FULL_DIGEST

A local import preserves historical governance and publishes native observations; it does not replace destination policy, install signing identities or adopt audit authority. local/governance=preserve explicitly records that choice. Other source findings may need their own decisions. Initialize a destination signing identity explicitly with hn init when authoring new native work. Imported foreign authors, reviews, CI records and workflows remain descriptive content.

Archive and saved sources retain their complete immutable snapshot scope. They reject --ref/--kind overrides. Local sources use explicit refs; provider sources require explicit --kind selections. A comment selection is separate from its parent issue selection.

Select an installed provider and exact profile

hn migrate capabilities lists compiled provider kinds without credentials, network requests or guessed support. capabilities --source-kind ... or --destination-kind ... displays the actual selected instance's offline metadata after construction. This is not proof of live access. Native/local/archive/saved inspection needs no provider, account, credential or signing identity.

Every foreign source and destination requires explicit nonempty --SIDE-api-version, --SIDE-credential-mode and --SIDE-endpoint. Guided mode asks for these strings and the selected adapter validates them. Versions and authentication are adapter-owned; the CLI does not infer them from capability text or silently select a future default.

Kind Explicit API version/profile Credential mode Scope and limits
github 2022-11-28 bearer owner/repository; issue/comment writes; open state and empty issue type only.
gitlab v4 private-token Explicit /api/v4 root, including self-hosted prefixes; nested project selector; ordinary issues/notes. Slash-leading body lines, including fenced examples, refuse hosted recreation to avoid quick actions.
forgejo 11 or 16 token Explicit /api/v1 root; repository selector; numeric existing label IDs.
gitea 1.24 or 1.27 token Separate tested profile; numeric label IDs; 1.27 updates require observed content_version. Neither Forgejo nor Gitea PATCH changes labels.
bitbucket 2.0 anonymous, oauth2, api_token_bearer, api_token_basic Cloud repository/PR-conversation/workflow preservation. Removed issue APIs and destination PR recreation are unavailable in v1. No Jira substitution or app passwords.
azure services-7.1 or server-7.0 bearer or pat Organization/collection and project are required. Repository is optional for Boards-only work; code, PRs and YAML require it. Explicit process/type/state mappings are required.

Use --SIDE-organization, --SIDE-project, and --SIDE-repository as the selected adapter requires. GitLab planning selectors are separate board IDs (--source-planning ID), not repository names. --source-workflow-ref pins workflow source; Bitbucket requires a full immutable commit hash. Retrieved workflow source is inert. --source-attachments FILE accepts a strict bounded JSON array of AttachmentSelection objects (URL, Name, Parent), with parent keys in the shared migration schema. Attachment bytes are fetched only through the selected adapter's origin and budget checks, never by following body links.

Supply API credentials afresh in a named environment variable with --source-token-env NAME or --destination-token-env NAME. If omitted, only the selected registration's TokenEnv, when present, is consulted. Azure has no registered token environment default: name the variable explicitly. Other providers' credentials are not read. Nonanonymous Bitbucket construction needs genuine supplied credentials even for metadata. For api_token_basic, the transient value is email:api-token; the email is not persisted separately. anonymous requires an empty credential. The CLI never fabricates a token or changes the chosen mode to make construction succeed.

Before either guided or saved apply, a nonanonymous foreign mode requires a fresh nonempty API credential whenever the saved plan contains API effects. An anonymously writable destination cannot silently substitute anonymous writes for that chosen mode. Public metadata/read-only planning, native/archive effects and Git-only intents remain independent of API write credentials.

The saved sealed cli.json is a strict bounded allowlist: schema, canonical plan digest, normalized destination locator and the exact accepted API version and credential mode. It contains no token, environment name or arbitrary config. Missing, modified or mismatched metadata refuses apply. Session files contain preserved source content and a local authentication key: keep them private.

GitLab to GitHub with code and communications

The following illustrates already-present code and explicit ordinary issue/note conversion. Set the selected credential variables before invoking it.

hn migrate plan --source-kind gitlab --source-endpoint https://gitlab.example/api/v4 \
  --source-repository group/project --source-api-version v4 \
  --source-credential-mode private-token --source-token-env SOURCE_API_TOKEN \
  --destination-kind github --destination-endpoint https://api.github.com \
  --destination-repository owner/project --destination-api-version 2022-11-28 \
  --destination-credential-mode bearer --destination-token-env DESTINATION_API_TOKEN \
  --kind issue --kind comment --source-git /work/project --ref refs/heads/main \
  --git-remote /remotes/project.git --map refs/heads/main=refs/heads/main \
  --map state:opened=open --map type:issue= --session /private/move-plan

This first read-only preview reports any outstanding decisions; supply each specific --decision FINDING_ID=preserve or =acknowledge after inspecting its message, or rerun with --guided. No usable plan is saved while decisions are missing. IDs come from that preview; there is no wildcard acknowledgment. Additional --map label:OLD=NEW and --map assignee:OLD=NEW selections are explicit. An empty value is meaningful omission. map decisions are valid only when the adapter's resulting finding is actually mapped. In guided mode choosing map collects concrete mappings and replans against the same source observation.

Capabilities and findings distinguish mapped, preserved, requires_mapping, unsupported, missing_dependency and identity_conflict. A preservation or acknowledgment decision cannot fabricate an omitted issue/comment intent. Review the saved intent list: a source with only preserved records does not imply destination-native recreation. Retrieved unsupported PR/MR/review/planning/workflow content remains in the saved snapshot. Inaccessible content is reported unavailable, never claimed preserved. Inspect exact retrieved blob bytes with inspect --session DIRECTORY --blob sha256:FULL_DIGEST (or --archive FILE).

Code uses the shared Git transport with explicit ref mappings and observed-old guards. There is no implicit clone, fetch, force push or source deletion. API credentials are not reused for Git. For private HTTPS Git, explicitly provide --git-origin HTTPS_ORIGIN --git-auth-env NAME and optionally --git-ca-file PATH. That variable contains the transient HTTP Authorization value for that origin. Supply these again on apply/resume. Ambient Git helpers and redirects cannot forward credentials to another origin. The saved git_refs intent owns the remote/ref bindings; the CLI does not duplicate or replan them on resume.

Apply, uncertainty and offline continuation

hn migrate inspect --session /private/move-plan
hn migrate apply --session /private/move-plan --confirm-apply \
  --destination-token-env DESTINATION_API_TOKEN
hn migrate resume --session /private/move-plan --confirm-apply \
  --destination-token-env DESTINATION_API_TOKEN

Resume reopens authenticated saved bytes and reconstructs the exact destination profile with fresh credentials. It neither re-fetches the source nor calls its snapshot planner. The engine resolves comment parents from confirmed issue receipts, persists the exact bound payload before the one write, and reconciles an uncertain write by exact positive evidence. Missing, ambiguous or inaccessible proof requires manual recovery; retrying never authorizes a duplicate create. Stale Git/provider baselines and hostile paths refuse before dependent writes. There is no global transaction spanning independent APIs, Git and files: inspect partial progress rather than deleting or editing checkpoints. Preserve the original session when recovery needs investigation.

After provider servers and credentials are gone, use a noforge build:

hn migrate inspect --session /private/move-plan
hn migrate plan --source-kind saved --source-repository /private/move-plan \
  --destination-kind local --destination-repository /work/local \
  --decision local/governance=preserve --session /private/bring-home

Carry forward any source findings' explicit decisions from this preview. Apply then inspect the digest listed by imports. To retain previously imported foreign records alongside current code, select each imported snapshot explicitly with --source-kind imported --source-repository /work/local --snapshot DIGEST, and optionally --source-git /work/local --ref refs/heads/main. Archive that composed observation through the same engine. Unselected imports are not claimed as read. nogithub excludes GitHub alone; noforge excludes every network adapter while keeping native collaboration, archives, imported records, Git and audits.

All CLI snapshots use the shared positive default budgets: 10,000 records, 1,024 blobs, 16 MiB per blob/manifest, 256 MiB attachment data, 100,000 Git objects, 16 MiB per Git object and 512 MiB total Git material. Limit exhaustion refuses incomplete success. Archive admission rejects traversal, duplicates, links, unexpected members, digest errors and incomplete native closure before visibility. Imports do not activate workflows, permissions, audits, synchronization or any background service. Existing hn transfer commands retain their own contracts.