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

Optional forge interoperability

On this page

Hubnot stores issues, planning projects, discussions and workflows as signed Git facts. Those resources work without GitHub, a gh executable, a service or a database. hn gh issue is a parallel local command interface. Network transfer and project migration are separate, explicitly invoked operations. Provider adapters are optional; their accounts, SDKs and services are never native requirements.

Native guides: issues, projects, discussions, workflows.

Project migration and provider profiles

For whole-project ownership, use guided/scripted migration, portable snapshots and the saved-plan engine. The explicit legacy hn transfer commands below retain their resource behavior. These two paths share native trust boundaries but have distinct container and recovery contracts. hn migrate capabilities reports the compiled registrations; select the exact endpoint, version, scope and credential mode deliberately.

Provider Tested profile Direction and limits
GitHub 2022-11-28 Issue/comment read/write; open state and empty issue type. PR/review/planning/workflow source preservation is not equivalent recreation.
GitLab v4, explicit self-hosted /api/v4 root Issue/note read/write, nested project selectors and retrieved MR/thread/planning/workflow preservation. Slash-leading body lines, including fenced examples, refuse hosted recreation to avoid quick actions.
Forgejo / Codeberg 11, 16, explicit /api/v1 root Separate issue/comment read/write profile; existing numeric label mappings. Instance availability and workflow source limits remain explicit.
Gitea 1.24, 1.27 Separate tested issue/comment profile; 1.27 update requires observed content_version. Neither Gitea nor Forgejo PATCH changes labels.
Bitbucket Cloud 2.0 Source-only repository/PR-conversation/workflow preservation; retired issues and destination PR recreation unavailable. No Jira substitution or app passwords.
Azure DevOps Services services-7.1, Server 2022 server-7.0 Mapped work-item/comment read/write. Organization/collection and project required; repository optional for Boards-only work, required for code/PR/YAML. Explicit process/type/state mappings and supplied Entra bearer/PAT; no OAuth registration.

Mapped native/remote issue content, raw retrieved source bytes and destination recreation are different capabilities. A source URL or unread/private resource is not preserved data. Preserve supported PR/MR/review/thread/planning detail as attributed records and digest-verified source blobs; inspect exact bytes offline. Unsupported creation, missing dependencies and identity conflicts require explicit decisions or refusal before writing. Imported users, permissions, reviews, CI and workflows confer no native policy or runner authority. Workflow source remains inert unless explicitly converted and separately authorized. No provider CLI, SDK, account provisioning, deletion or automatic sync is added.

Build with optional adapters

Go 1.26+ and Git 2.x are required. The product uses the Go standard library and Git; there is no external Go module or gh subprocess dependency.

go build -trimpath -o hn .
go build -trimpath -tags nogithub -o hn-local .
go build -trimpath -tags noforge -o hn-native .
go list -deps -tags noforge .

nogithub excludes only hubnot/internal/githubapi; other adapters remain. noforge excludes every network client/registration. All variants retain native issue, project/item, discussion/reply, workflow and local hn gh issue operations. Previously imported facts remain readable and editable with the native commands. hn-local github help explains transport exclusion; GitHub transfer operations fail as an unavailable provider before network access. Local/archive migration, imported foreign inspection, independent audit execution and explicit follow-ups also remain available with noforge. No account or token is needed for local operation. Private native keys are needed for new native signed facts, not ordinary reads.

Local gh-style issue interface

This is hn gh, not an executable replacement for GitHub's gh. It never contacts a forge, reads GitHub credentials, fetches tickets or performs sync.

Command Supported flags
hn gh issue create Required --title/-t; --body/-b, --label/-l, --assignee/-a
hn gh issue list --state/-s open\|closed\|all, --label/-l, --assignee/-a, --limit/-L, --json FIELDS
hn gh issue view ID --comments, --json FIELDS
hn gh issue edit ID --title/-t, --body/-b, --add-label, --remove-label, --add-assignee, --remove-assignee
hn gh issue comment ID Required nonblank --body/-b
hn gh issue close ID / reopen ID No lifecycle-specific flags

Every supported command accepts --help/-h. Value flags accept --flag=value or a separate argument. Flags may appear before or after the ID; -- ends flag parsing. Label and assignee values may repeat or contain comma-separated names; they form sets. List filters require every selected label/assignee and apply before the limit (default30, maximum100000). Conflicted issues remain visible with unknown state. Mutations require the full native sha256:<64-hex> issue ID; view accepts a unique native hexadecimal prefix. Numeric GitHub issue numbers and --repo are not native identifiers.

hn gh issue create -t "Improve setup" -b "Document the first successful run" -l docs
hn gh issue list --state all --json id,title,state,conflict
hn gh issue view <full-native-issue-id> --json id,title,body,comments
hn gh issue edit <full-native-issue-id> --add-label onboarding
hn gh issue comment <full-native-issue-id> -b "A local signed comment"
hn gh issue close <full-native-issue-id>
hn gh issue reopen <full-native-issue-id>

Successful compatibility mutations print the full issue ID as plain text. They observe the native head and use the same signed writer as hn issue; conflicts refuse a hidden winner. Use native hn issue for explicit expected heads, operation keys and conflict resolution. Unsupported flags, JSON fields, gh pr, gh run, gh api, GraphQL forwarding, and other command families fail with a nonzero exit. There is no unmodified-gh HTTP compatibility service.

Compatibility JSON

--json is a comma-separated, duplicate-free selected field list, supported on list and view. List emits an array; view emits one object. This is separate from the native hn.issue/1 envelope.

Field Value
id Full stable native issue ID
title, body Strings, or null for a conflicted state
state "OPEN", "CLOSED", or null for a conflict
labels Array of {"name":"docs"}, or null for a conflict
assignees Array of {"login":"name"}, or null for a conflict
author {"login":"<native opening actor fingerprint>"}
url Recorded descriptive source URL, otherwise null
conflict Boolean
comments View only: array of {id,body,author:{login},createdAt}

Comment authors are native signers, including for attributed imported comments; source authors/times remain in the attributed text and metadata. Empty arrays are complete empty results, not missing pages. Read failures and stale snapshots refuse incomplete answers. For example:

[{"id":"sha256:<64-hex>","title":"Improve setup","state":"OPEN","conflict":false}]

Explicit transfer commands

hn transfer capabilities --provider github [--resource issue|project|workflow|discussion]
hn transfer import --provider github --project SELECTOR [--resource RESOURCE] [--key KEY] [--endpoint REST_BASE] [--map KEY=VALUE]... [--dry-run]
hn transfer export FULL_NATIVE_ID --provider github --project SELECTOR [--resource RESOURCE] [--endpoint REST_BASE] [--map KEY=VALUE]... [--dry-run]

issue is the default resource. Export's full native ID precedes the flags. Import --key selects one external record where supported; it is never a native mutation ID. --provider and --project are explicit. Each mapping key may appear once; at most64 nonempty KEY=VALUE mappings are accepted (key128 bytes, value4096 bytes). Unknown or unused mappings fail rather than silently changing meaning.

The installed capability report is JSON with provider and capabilities. Each capability names resource, import, export, mapped_fields and unsupported, with a reason when unavailable. Typed migration plans use resource, direction, nodes and findings; finding statuses are mapped, preserved, requires_mapping, unsupported, missing_dependency and identity_conflict. A supported capability does not mean all foreign semantics can be converted. Inspect the plan/error and provide only intended mappings. Issue transfer prints a human import/export/validation summary rather than that typed plan JSON. Dry-run reads the selected provider and validates the plan, but creates no signed facts, export mutations or recovery checkpoint. It can still need provider read authorization.

The convenience aliases cover issues only:

hn github import --repo OWNER/REPO --issue 42 --dry-run
hn github import --repo OWNER/REPO --issue 42
hn github export <full-native-issue-id> --repo OWNER/REPO --dry-run

Their flags are --repo, --api-url, --dry-run, and import-only --issue. Omitting --issue imports the bounded issue listing. Use canonical hn transfer for the other resources and repeated --map options.

Supported GitHub mappings

Resource Selector and supported transfer Required decisions / manual limits
Issue --project OWNER/REPO; import --key 42 or bounded listing. Title, body, open/closed, labels, assignee claims and attributed comments No --map keys. PRs are excluded. Milestones/reactions are not migrated; attachment references remain external URLs. Native-only fields use the export representation described below.
Discussion --project OWNER/REPO; import numeric --key or listing. Thread/replies, one parent level, source attribution, category, state and current answer content --map history=snapshot in both directions; export --map category=EXISTING_GLOBAL_CATEGORY_ID. Poll/lock/vote/reaction/minimized/deleted observations require --map losses=acknowledge. No complete edit/deletion history, category creation, moderation transfer or automatic historical-answer replacement.
Project Planning board selectors user:LOGIN/NUMBER, org:LOGIN/NUMBER, node:GLOBAL_ID; export may select an owner user:LOGIN or org:LOGIN for creation Supported text/number/date/single-select fields, table/board layouts, draft or explicitly mapped items, values/order/archive. Content references and each unsupported setting/field/view omission require explicit mappings. Project automation, permissions and arbitrary schema migration are not equivalent.
Workflow --project OWNER/REPO; import optional workflow --key and --map source-ref=REF. Read exact YAML at a resolved commit Source is inert by default. Explicit native pipeline/generated-subset mapping is required for native execution. Export requires format, branch, path and expected blob SHA, plus explicit execution environment choices for generated Actions. Secrets, arbitrary Actions features and provider enable/disable authority are not migrated.

See discussion, project and workflow guides for exact resource flags, JSON state examples, per-finding mapping keys and refusal boundaries. Project board ownership is separate from repository ownership; OWNER/REPO is not a Projects V2 selector.

hn transfer import --provider github --resource discussion --project OWNER/REPO \
  --key 7 --map history=snapshot --dry-run
hn transfer export <full-discussion-id> --provider github --resource discussion \
  --project OWNER/REPO --map history=snapshot --map category=<global-category-id> --dry-run
hn transfer import --provider github --resource project --project org:TEAM/3 --dry-run
hn transfer import --provider github --resource workflow --project OWNER/REPO \
  --key 12 --map source-ref=main --dry-run

Copying a workflow file to an explicit branch can activate provider triggers. A successful transfer is not a claim that foreign jobs, runner isolation, policy, secrets or permissions have become equivalent to native execution.

Access and transport bounds

Only explicit transport commands contact the selected adapter. HN_GITHUB_TOKEN is read by that adapter's factory and is never stored in signed facts, private checkpoints or diagnostics. REST issue/workflow public reads may be anonymous; private reads and all writes need a token with sufficient repository access. Every GraphQL read, including public discussions and Projects V2, requires authentication. The token must also have the selected resource's read/write permissions. Missing scopes, inaccessible data and provider policy failures are errors, not empty resources. Core commands and hn gh do not inspect this token.

The default REST base is https://api.github.com. Override it explicitly with --endpoint or issue-alias --api-url, for example a GitHub Enterprise https://HOST/api/v3 base. GraphQL derives /api/graphql from that enterprise base, and /graphql from the root base; it never appends /graphql to /api/v3. HTTPS is required except explicit loopback-IP HTTP for local fixtures/development. URL userinfo, query/fragment, ambiguous base paths, credential-bearing redirects and cross-endpoint pagination are refused. No source URL from imported metadata is fetched merely because it is present.

Bound Limit
Whole transfer command deadline 5 minutes
Individual HTTP request 30 seconds
HTTP response 4 MiB
SDK method aggregate response bytes 64 MiB
REST listing / GraphQL connection 100 pages, at most100 nodes per page
Provider-neutral typed graph / nodes 8 MiB / 256
Private transfer checkpoint, including future progress/results 8 MiB
Individual workflow source document / workflows per read 1 MiB / 256
Native issue body/comment 64 KiB, plus the complete256 KiB signed-event bound

Native project/discussion/workflow limits and verified Git reader budgets are also enforced. Attribution, source metadata, JSON escaping and publication markers consume those bounds. A foreign record that cannot fit is rejected, not truncated. GraphQL errors or partial data fail even with HTTP200; malformed, ambiguous or invalid-Unicode JSON and incomplete/cyclic pagination fail closed. Transport errors exclude remote response bodies and credentials. Mutations are not automatically retried.

Provenance, repeat and conflict behavior

Imports create normal native signed facts. Original provider author, timestamps, URLs and IDs are source claims, not a native signer or policy role. Labels, assignees, closed state, project visibility, categories and source moderator state grant no review, merge, runner or execution authority.

Issue opening metadata uses forge/source; current signed observations and managed baselines use forge/observation and forge/baseline. Typed resources use forge/resource-source, forge/resource-observation and forge/resource-baseline. Stable source identity includes provider, endpoint, kind where applicable, and immutable provider ID; mutable lookup selectors do not replace it. Mappings and baselines are checked against verified signed import operations, not trusted merely because current metadata contains them.

Repeated unchanged imports do not duplicate records. Changed source can update an unchanged native projection; unchanged source preserves local edits. Competing native/source changes, conflicting native heads, duplicate identity claims, missing dependencies and ambiguous publication markers refuse automatic resolution. Imports preflight complete selected records/graphs, but multi-event imports are resumable work, not transactions: interruption can leave valid partial native progress, recovered by the same exact operation intent.

Issue export to a new destination preserves criteria, relations and nonreserved metadata in a readable descriptive JSON appendix with native attribution and a terminal structured proof. These fields do not become GitHub checklists, relationships or authority. Returning an imported issue to its verified exact source keeps its ordinary body; extra native-only criteria/relations/metadata then require an explicit lossless source-body mapping, which is not implemented, so export refuses. Verified source-origin comments are not echoed back. Source comment edits append attributed native corrections; deletions do not erase immutable native history. Exported comment receipts preserve remote edits or deletions instead of recreating old comments.

A source roundtrip uses stable identity and verified provenance to avoid footer feedback. Re-importing an exact native publication without proven mapping refuses duplicate/remapping. Terminal markers are descriptive and forgeable; marker text alone never grants native authority. Ordinary malformed marker-like prose remains content. If a remote-only issue body/footer edit destroys the association, an unchanged already-published native snapshot can be preserved read-only; new unpublished work requires explicit manual association recovery. That path does not adopt the external edit as a new overwrite baseline.

Private recovery and publication

Private state lives under hn/forge in the repository's resolved Git directory, usually .git/hn/forge: directories0700, regular files0600, atomic replacement. It is not Git protocol authority, is not cloned, and contains no provider token. Removing the adapter or keys does not remove imported signed native content.

Issue, discussion and project real exports require the active native signing identity to authenticate their complete canonical private recovery journals. Use that same identity for resume. Identity switches, unsigned old journals, altered progress/bindings or noncanonical envelopes refuse with an explicit recovery error. Dry-run remains private-key-free; an existing signed journal can be checked with its public proof. The signature authenticates local export intent and progress; it does not prove a remote write succeeded or grant any provider permission. Workflow recovery instead binds its exact native snapshot, explicit destination, source bytes and guarded blob-write intent; it does not create a new native execution authorization.

Only one local transfer may hold the private transfer.lock. After a crash, first establish that no transfer is running before removing a stale lock; do not delete a journal to bypass uncertainty. Multiple publishers/clones are not a distributed lock and cannot promise globally exactly-once creation.

Each external mutation is a separate prepared → in_flight → confirmed step. Intent is persisted before the one write. On a lost response, retry the same command and selection: recovery must find one exact positive match for its intended identity/content. A complete negative scan is not proof that a write never happened and does not authorize another creation. Ambiguous, changed, deleted or inaccessible results require manual reconciliation. clientMutationId is correlation, not an idempotency guarantee. A native change made during recovery is a later export; the saved snapshot is not silently refreshed.

Remote APIs do not provide an atomic compare-and-swap for all collaboration fields. Baseline checks narrow the race but cannot eliminate an external edit between read and write. Inspect manual failures, preserve the journal and remote data, and recover deliberately. No transfer command provides background sync, a merge policy engine, automatic protected checks, automerge, or GitHub-wide API emulation.

Adding another provider

For legacy transfer, the internal extension seam is internal/forge: an adapter supplies its bounded identity, opaque IDs and selectors, issue methods and/or the selected typed resource-reader interfaces. An optional registration supplies a factory, its credential variable, explicit capabilities and resource export callbacks. Transfer planning, signed native storage and recovery stay provider-neutral; HTTP clients and provider DTO conversion stay inside the adapter. A new adapter must validate its own network endpoints, implement complete bounded reads and single-mutation writes, and report missing/lossy mappings before mutation.

This is an internal implementation boundary, not a runtime plugin loader or a stable external SDK promise. No unspecified alternate forge or workflow format is guessed. A provider's API or CI engine becoming unavailable cannot prevent native Git-backed collaboration.

Project migration adapters instead implement internal/migration source, destination, capability, immutable identity, blob, receipt and exact reconciliation contracts. Dependent issue/comment writes bind confirmed parent receipts through the shared engine, never provider-local hidden state. Keep nonvacuous direction/version conformance and bounded endpoint/pagination checks; see migration engine. Saved plans contain no credentials; API effects require fresh explicitly supplied credentials at apply/resume.