Skip to content

GitHub Issues linking

Source: packages/core/src/git/github.ts, packages/cli/src/commands/link.ts · Spec: §12, §14 (M4)

Sessions carry links.githubIssue (schema §3.3) so Beetl can complement an issue tracker rather than replace it (plan Q3). beetl link is the write path.

links.githubIssue stores the full {owner, repo, number, url} identity. A URL keeps its own owner/repo — cross-repo links are preserved verbatim, never rebound to the current remote — and a bare number resolves against the origin GitHub slug or is rejected (INVALID_INPUT) when there is none. Legacy stores may hold a bare number: it stays parseable, but its provenance is unknowable, so it reads as legacy-unknown (never silently the current repo), renders as #n (legacy-unknown) with no URL, and beetl doctor warns (LEGACY_ISSUE_LINK) until the session is re-linked with the full issue URL.

  • beetl link <ref> <issue> — record an existing issue. Accepts 123, #123, or a full https://github.com/<owner>/<repo>/issues/<n> URL.
  • beetl link <ref> --create — file a GitHub issue from the session via the gh CLI (gh issue create --title … --body-file -) and link the returned number. The body mirrors the show --md shape (report / reproduction / diagnosis / fix sections that are present) with a beetl-session footer. Requires an installed, authenticated gh; failures map to GH_FAILURE (exit 1).
  • beetl link <ref> --clear — remove the issue link (githubIssue: null).
  • beetl link <ref> --related <refs...> — cross-link other sessions into links.related (deduped, validated to exist, self-links rejected).

<issue>, --create, and --clear are mutually exclusive; a bare link with no operation is INVALID_INPUT. Links land through applyUpdates, so the session logs a normal update event and terminal sessions stay read-only.

--json returns the standard { session, ordinal } envelope plus issueUrl when the URL is known — always for --create and for every full-identity link (the URL comes from the stored identity, never reconstructed from the current remote); absent only for legacy legacy-unknown links. show/ show --md render GitHub issue (owner/repo#n) and Related rows.

All gh/git calls go through an injectable GithubExec (packages/core/src/git/github.test.ts uses fakes; the CLI test stubs a gh script on PATH, skipped on Windows). Core stays terminal-free; the CLI never calls gh unless --create is passed.