Skip to content

Drop the release workflow; this fork ships no releases - #14

Closed
bcarlso-lt wants to merge 14 commits into
voska:mainfrom
leantechniques:chore/disable-fork-releases
Closed

bcarlso-lt wants to merge 14 commits into
voska:mainfrom
leantechniques:chore/disable-fork-releases

Conversation

@bcarlso-lt

Copy link
Copy Markdown

Distribution is a signed, notarized .pkg built by qbo-cli-deploy from a tag and delivered via Intune. A release workflow here isn't merely redundant — it's harmful.

Why

It would publish broken macOS binaries. .goreleaser.yaml is inherited from upstream and builds with CGO_ENABLED=0. The Keychain backend (99designs/go-keychain) is cgo, so such a binary silently falls back to a passphrase-prompting file keyring. That is the exact bug qbo-cli-deploy@5de5105 fixed by building CGO_ENABLED=1 on a macOS runner. A Release published from here would offer a broken download that's easier to find than the real .pkg.

Upstream isn't wrong to use CGO_ENABLED=0 — cross-compiling darwin with cgo from an Ubuntu runner needs a toolchain goreleaser isn't set up for. That constraint is precisely why the .pkg pipeline exists, runs on macos-latest, and uses lipo.

It could never have completed anyway. The brews:/scoops: blocks target voska/homebrew-tap and voska/scoop-bucket using HOMEBREW_TAP_GITHUB_TOKEN — a secret this fork does not have (it has zero Actions secrets). Consistent with that, release.yml has never run, and this fork has no GitHub Releases despite carrying tags v0.1.0–v0.8.0.

Why only the workflow, not .goreleaser.yaml

upstream-sync merges upstream/main on a schedule. Deleting the config would create permanent divergence against a file upstream actively maintains, so every future upstream edit would conflict — a recurring tax for no extra safety. Keeping its body byte-identical to upstream costs nothing.

Making the deletion durable

The deletion is not self-sustaining. upstream-sync.yml's resolve prompt lists the LT customizations to preserve (internal/entra, internal/vault, the bootstrap tier, bouncer login) and then says "For everything else, prefer upstream's version." On the first upstream edit to release.yml that becomes a modify/delete conflict, and the resolver would restore the file — after which the next fork tag would run goreleaser again, publishing Releases that bump-version assumes don't exist and failing on the missing secret.

So this PR also:

  • records the intent in the resolve prompt: keep the file deleted on a modify/delete conflict, with the reason;
  • asserts it deterministically in Verify Claude's result — ! test -f .github/workflows/release.yml — so it's checked, not trusted;
  • adds a header comment to .goreleaser.yaml warning it must not be run here, since with no workflow calling it a local goreleaser release would push fork-built artifacts at upstream's tap. The resolve rules explicitly say to keep that comment when taking upstream's version of the body.

Verified

  • Nothing else references release.yml; no workflow (ci.yml, pages.yml, upstream-sync.yml) invokes goreleaser after this.
  • No dispatch breaks: an earlier design had this fork dispatch to qbo-cli-deploy via a DEPLOY_DISPATCH_TOKEN PAT (fork beads task .17), but that was replaced by the polling model — bump-version's header records that the fork "stays org-agnostic… this repo polls… rather than the fork dispatching here."
  • README.md/SECURITY.md install instructions point at upstream's tap and Releases, which exist independently, so they remain truthful.
  • Both edited YAML files parse; the new verify assertion passes against this tree.

Noted separately, not fixed here

skills/qbo/SKILL.md:13 tells users to brew install voska/tap/qbo. Upstream's newest tag is v0.6.1, while the marketplace plugin states "Requires qbo CLI v0.8.0+ (Key Vault credential bootstrap)". Anyone following the skill's own install instructions therefore gets a qbo below the stated minimum and without the vault bootstrap. That file is what the marketplace plugin ships. Worth its own change.

Companion PR: leantechniques/qbo-cli-deploy#4.

🤖 Generated with Claude Code

bcarlso-lt and others added 14 commits August 19, 2026 12:35
The file backend previously encrypted tokens and client credentials with
an empty passphrase, making the on-disk encryption a no-op. Now the
passphrase comes from QBO_KEYRING_FILE_PASSWORD or an interactive prompt
(memoized per process so multi-open commands prompt once), and headless
invocations fail loudly when neither is available. --no-input suppresses
the prompt. A set-but-empty env var is rejected explicitly, and a wrong
passphrase surfaces as a config error naming the variable instead of
'not authenticated'.

BREAKING: entries stored by older versions used the empty passphrase and
can no longer be decrypted — re-run qbo auth login (and qbo auth
set-client) once after upgrading.

Co-authored-by: Claude Fable 5 <[email protected]>
The save path for qbo download without -o comes from the API response,
which anyone with write access to the QBO company controls. Previously
os.Create silently truncated whatever that name pointed at in the
working directory.

Now the destination is created with O_EXCL and an existing file (or
planted symlink) is a usage error unless --force is passed; the
existence check runs before any API calls are spent. API-supplied names
are reduced to a safe basename (both separator styles) and hidden-file
names like .envrc are refused outright. --force writes to a temp file
and renames into place, so a mid-stream failure preserves the original
and a symlinked name is replaced rather than written through. Close
errors now fail the command instead of reporting a corrupt file as
saved.

Co-authored-by: Claude Fable 5 <[email protected]>
QBO string fields (customer names, memos, line descriptions) are often
populated by external parties and were rendered verbatim in human and
plain modes, letting embedded ANSI/OSC sequences rewrite or spoof
terminal output — the same class git, gh, and kubectl have patched.

Table cells and headers now drop C0/C1 controls and DEL, strip bidi
overrides/isolates and zero-width characters, and flatten CR/LF/tab to
spaces so hostile values can't forge rows or TSV columns. The stderr
helpers (Hint/Success/Warn/ErrorMsg) sanitize too, covering API error
bodies and API-supplied file names; message newlines are preserved.
JSON mode is unchanged — encoding/json already escapes control chars.

Co-authored-by: Claude Fable 5 <[email protected]>
Read /Library/Application Support/qbo/bootstrap.json (installed by device
management) carrying vault URL, secret name, and Entra tenant/client ID so
a first login can fetch Intuit client creds from Azure Key Vault without
the binary carrying org-specific values. auth status reports the tier
(bootstrap-pending/bootstrap/bootstrap-error) and now emits credential
diagnostics even before a company exists; auth login names the vault on
provisioned machines and fails loudly (exit 10) on a malformed bootstrap
file. Keyring creds gain an origin marker for the upcoming vault self-heal.

Co-Authored-By: Claude Fable 5 <[email protected]>
Check the os.Unsetenv return in token_test.go and replace a raw invisible
U+009B control character in write_test.go with its Unicode escape sequence.

Co-Authored-By: Claude Fable 5 <[email protected]>
Ignore local Dolt/beads state and add the bd init workflow block to
AGENTS.md. The duplicate Codex-flavored block from bd setup codex was
removed along with the .codex and .agents artifacts.

Co-Authored-By: Claude Fable 5 <[email protected]>
On a provisioned machine with no configured client creds, qbo auth login
now signs in to Entra ID (pure-Go MSAL, browser or --device-code, token
cache in the qbo keyring) and fetches the Intuit client credentials from
the org's Key Vault, storing them with origin=bootstrap before the Intuit
OAuth flow. --no-input allows only the cached silent path (exit 4).

The bearer token is only ever sent to https Azure Key Vault hosts; sign-in
failures map onto the exit-code contract (6 for Conditional Access blocks
and vault 403s, 4 for cancellations, 8 for network, 10 for misprovisioned
secrets). set-client --clear also removes the cached Entra session.

Closes qbo-cli-iyj.2.

Co-Authored-By: Claude Fable 5 <[email protected]>
When a token refresh fails with invalid_client (typed RFC 6749 check, not
string matching) and the credentials are vault-owned — bootstrap origin in
the keyring with no env overrides — silently re-fetch them from Key Vault
using the cached Entra session and retry once per invocation, preserving
the stored redirect URI. User-supplied credentials are never auto-replaced;
any other failure guides re-login (exit 4).

The Entra browser wait now times out after 5 minutes like the Intuit flow,
every MSAL network leg is bounded by a 30s HTTP client so headless
self-heal can't hang, the silent path picks the cached account matching
the configured tenant, and a stalled vault fetch is retryable (exit 8).

Closes qbo-cli-iyj.3, qbo-cli-iyj.4.

Co-Authored-By: Claude Fable 5 <[email protected]>
Intuit production apps reject http://localhost redirect URIs, so a hosted
HTTPS page must receive the callback and forward it to the CLI. Route all
redirect URIs through the local listener: a non-local redirect_uri is sent
to Intuit (auth URL and token exchange) while the callback is still caught
on localhost:8844. The --manual flag, previously declared but unused, now
explicitly selects the paste-the-callback-URL flow. Skill docs updated to
match.

Also fix a latent race (callback handler signaled the result/error channels
before writing the HTTP response, letting server.Close() kill the connection
mid-response) and a listener leak on the GenerateState error path.

Co-Authored-By: Claude Fable 5 <[email protected]>
Bash(qbo *) pre-authorized mutating commands (create/update/delete/batch)
whenever the skill was active, bypassing the permission prompt. Reads stay
frictionless; writes now require explicit user approval.

Co-Authored-By: Claude Fable 5 <[email protected]>
Weekday-cron workflow that merges voska/qbo-cli upstream changes into a
SHA-keyed sync branch, runs the quality gates (build/test/vet/lint), and
opens a review-gated PR. Dirty merges are resolved by claude-code-action
authenticated via workload identity federation (no repo secrets), running
without ambient git credentials, then re-verified deterministically —
including that fork history and LT customizations survived — before the
PR opens.

Co-Authored-By: Claude Fable 5 <[email protected]>
Distribution is a signed, notarized .pkg built by qbo-cli-deploy from a
tag and delivered via Intune. A release workflow here is not just
redundant, it is harmful:

- `.goreleaser.yaml` is inherited from upstream and builds macOS with
  CGO_ENABLED=0. The Keychain backend (99designs/go-keychain) is cgo, so
  such a binary silently falls back to a passphrase-prompting file
  keyring — the exact bug qbo-cli-deploy@5de5105 fixed by building with
  CGO_ENABLED=1 on a macOS runner. Any Release published from here would
  offer a broken macOS download that is easier to find than the .pkg.
- Its `brews:`/`scoops:` blocks target voska/homebrew-tap and
  voska/scoop-bucket using HOMEBREW_TAP_GITHUB_TOKEN, which this fork
  does not have. The workflow could never have completed; it has never
  run.

Deleting only the workflow, not `.goreleaser.yaml`: the config stays
byte-identical to upstream's body so it contributes no recurring merge
conflicts, which matters because upstream-sync merges upstream/main on a
schedule.

That deletion is not self-sustaining, though. The resolve prompt's
catch-all is "prefer upstream's version", so the first upstream edit to
release.yml would surface a modify/delete conflict and get resolved by
restoring the file. So the intent is now recorded where the resolver
will read it, and asserted deterministically afterwards
(`! test -f .github/workflows/release.yml`) rather than trusted.

A header comment on `.goreleaser.yaml` says not to run it here — with
no workflow calling it, a local `goreleaser release` would otherwise
push fork-built artifacts at upstream's tap. The resolve rules call out
keeping that comment when taking upstream's version of the body.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@bcarlso-lt

Copy link
Copy Markdown
Author

Closing — opened against the wrong repository by mistake. This change is specific to the Lean Techniques fork (leantechniques/qbo-cli) and is not a proposal for upstream. Apologies for the noise.

@bcarlso-lt bcarlso-lt closed this Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant