Skip to content

feat(update): address envs by their printed key; add scope flags (#166)#167

Merged
fentas merged 7 commits into
mainfrom
feat/env-addressing-166
Jun 24, 2026
Merged

feat(update): address envs by their printed key; add scope flags (#166)#167
fentas merged 7 commits into
mainfrom
feat/env-addressing-166

Conversation

@fentas

@fentas fentas commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Fixes #166.

Problem

b update <arg> resolved every arg through the binary parser first (provider.ParseRef), which splits on @ and is not SSH-aware. So:

  • The exact key b env status prints for an SSH-keyed env (git@github.com:org/repo#main) could never be passed back — it parsed to git and failed as "unknown binary or env: git".
  • A path like github.com/acme/framework silently fell into binary space → "release not found".
  • --group filtered only envs while still updating every binary, so there was no way to refresh vendored env content without toolchain churn.

Root cause: one flat positional addresses two namespaces (binaries + envs) with overlapping grammars, and we committed to the binary grammar before the env namespace (which has the SSH-aware parser) was ever consulted.

Fix — resolve by config membership + an env marker

resolveUpdateArg now decides by what's actually in b.yaml, not by guessing from shape:

  • docker:// / oci:// / go:// → always binary (# there is a path/module char, never an env label).
  • A local path, or a # outside those protocols → env marker: resolve as env (SSH/local-aware), error if no such env (the user was explicit).
  • Otherwise → match envs by exact/canonical key before binary space, so the printed key round-trips and an env ref isn't hijacked by the github.com/org/repo provider convention. A trailing # forces env for keys that are also valid binary refs (dual-namespace escape hatch).
  • Typing the https path of an SSH-keyed env still resolves to an ad-hoc binary but prints a "did you mean env …" hint.

Scope flags

Notes

  • The issue's "minor" item (a generated b.yaml header saying "Run b env sync") is already gone from current source — nothing to change.
  • Deliberately not rewriting env keys in b.yaml on save; the membership resolver makes it unnecessary, and silently rewriting user keys is the class of bug we fixed in fix(state): keep relative file paths on save; clearer install errors #159.

Tests

pkg/cli/update_envaddr_test.go (19 cases): SSH-key round-trip, label-less SSH bare, trailing-# force, env-marker-no-match error, https-key-not-hijacked regression guard, claim-3 hint, docker-#-stays-binary, repoTail folding, scope-flag validation, and three runAll scoping tests. go build, go vet ./..., gofmt, full go test ./... all clean.

🤖 Generated with Claude Code

`b update <arg>` resolved every arg through the binary parser first, which
splits on `@` and is not SSH-aware. So the exact key `b env status` prints
for an SSH-keyed env (e.g. git@github.com:org/repo#main) could never be
passed back — it parsed to "git" and failed as "unknown binary or env".
`--group` only filtered envs while still updating every binary, so there
was no way to refresh vendored env content without toolchain churn.

Resolution is now driven by config membership and an env marker instead of
guessing from shape:

- docker:// / oci:// / go:// stay binary ('#' there is a path/module char).
- A local path, or a '#' outside those protocols, is an env marker: resolve
  as env (SSH/local-aware), error if no such env.
- Otherwise match envs by exact/canonical key before binary space, so the
  printed key round-trips and an env ref isn't hijacked by the provider
  convention. A trailing '#' forces env for keys that are also valid binary
  refs (the dual-namespace escape hatch).
- Typing the https path of an SSH-keyed env still resolves to an ad-hoc
  binary but prints a "did you mean env …" hint.

Scope flags:

- --group now implies env-only (groups are an env-only concept).
- add --envs-only / --binaries-only, validated as mutually exclusive and
  rejected alongside explicit args.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes b update <arg> ambiguity between binaries and envs (notably SSH-keyed env refs) by resolving args based on config membership and explicit env markers, and adds namespace scoping flags to update only envs or only binaries.

Changes:

  • Add resolveUpdateArg to correctly round-trip env keys (including git@...#label) and provide “did you mean env …” hints when users type an https form that maps to an SSH-keyed env.
  • Introduce update scoping via --envs-only / --binaries-only, and make --group imply env-only when running b update with no args.
  • Add docs + tests covering env address resolution, hinting, and scoping behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
pkg/cli/update.go Implements new arg resolution and scope flag validation + run-all scoping.
pkg/cli/update_envaddr_test.go Adds resolver and scoping tests for issue #166 regression coverage.
docs/b/subcommands/update.mdx Documents env addressing and new scope flags/--group semantics.

Comment thread pkg/cli/update_envaddr_test.go
Comment thread pkg/cli/update.go
@fentas

fentas commented Jun 24, 2026

Copy link
Copy Markdown
Owner Author

Review — solid fix; resolves the core of #166. Membership-based resolveUpdateArg (env-match before binary parsing) is the right call, and --group implying env-scope + --envs-only/--binaries-only closes the binary-churn half cleanly. Confirmed the printed SSH key round-trips. A few open points, mostly small:

1. Short-name addressing — b update lok8s# routes correctly but can't match (the main one)

The # correctly sends it down the env-marker branch, but matchEnv only does exact + canonical-key lookup:

if e := o.Config.Envs.Get(arg); e != nil { ... }              // "lok8s#" → no
if key := canonicalEnvKey(arg); key != arg { Envs.Get(key) }  // "lok8s"  → no

canonicalEnvKey("lok8s#")"lok8s", and the env is keyed git@github.com:org/repo#main, so you get unknown env: lok8s#. Since the printed key is long, a short handle is the ergonomic win — and repoTail already computes org/repo for the suggestEnv hint. Promoting it to a third matchEnv tier (exact → canonical → repo-tail) makes lok8s# / org/repo# first-class, with ambiguity → error listing the candidate keys.

(Side note: --envs-only as a selector — e.g. lok8s --envs-only — is the wrong layering, and it's already correctly rejected by the (EnvsOnly || BinariesOnly) && len(specifiedArgs) > 0 guard. The # marker is the better idiom.)

2. Trailing-# against a labeled env — worth a targeted test

canonicalEnvKey("org/repo#") reduces to base org/repo (label dropped). Does Envs.Get(base) match an env keyed org/repo#main? If Get is exact, the force-# could miss labeled envs. There's a "trailing-# force" case in the suite — just make sure one of them targets an env that actually has a #label.

3. (minor) https-path hint — surface in docs

github.com/org/repo still resolves to binary-space with the "did you mean env" note — good. A line in update.mdx ("to target an env, paste the key b env status prints, or append #") would save the round-trip.

4. (minor) the #166 doc item

You note the generated b env sync comment is already gone — I hit it on v4.13.0's emitted b.yaml, so just confirm the current generator/templates don't still write it.

Net: ship-able for the core; #1 is the one I'd fold in, since it's the natural follow-up to "address by the printed key."

🤖 via Claude Code

Copilot:
- reject contradictory flag combos in Validate(): --plan-json with
  --binaries-only (binaries never appear in the JSON plan), and --group with
  explicit args (--group is env-only; a non-matching group would otherwise
  silently drop the named env to a no-op). The latter also covers Copilot's
  "--group still runs named binaries" point and the subagent's group no-op.
- (refuted) "EnvList literals won't compile" — EnvList is []*EnvEntry and Go
  elides & in pointer-element composite literals; build + tests are green.

Gemini review:
- short env handles: matchEnv gains a third tier (only on the explicit '#'/path
  marker) that suffix-matches a repo basename or org/repo tail against env keys,
  so `lok8s#` / `org/lok8s#` reach `git@github.com:org/lok8s#main`. Ambiguous
  handles error with the candidate keys. Closes issue #166's short-handle gap
  and also makes a trailing '#' resolve a *labeled* env. Bare names (no marker)
  still never match an env, so they can't shadow a binary.
- consolidate repoTail + the new matcher onto a shared cleanRepoPath helper.
- docs: document short handles and the https-path → binary hint.
- confirmed the config generator writes no `b env sync` comment (v4.13.0 only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

Comment thread pkg/cli/update.go Outdated
Comment thread pkg/cli/update.go Outdated
Comment thread pkg/cli/update.go Outdated
Comment thread docs/b/subcommands/update.mdx Outdated
fentas and others added 2 commits June 24, 2026 15:00
The env-sync guide showed SSH-keyed envs and --group but never how to update
just one env — the exact gap behind #166. Add an 'Update a specific env'
section (exact key / SSH / #label round-trip, short handles, --envs-only) and
note that --group is env-only, cross-linking the b update reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…che dedup

Copilot:
- the https-vs-SSH guidance was wrong: appending '#' to an https arg
  (github.com/org/repo#) can't match an SSH-keyed env (the SSH identity has no
  host segment). The stderr note, the unknownArgError, and update.mdx now offer
  working forms only — the exact key or a short handle (org/repo#) — via a new
  envUpdateHint helper.
- plan-json with binaries configured but no envs emitted no JSON at all
  (pre-existing); runAll now counts binaries as "nothing" under --plan-json so
  it reliably prints []. Regression test added.

Subagent (round 2, fix-and-ship):
- move transport-stripped repo-path extraction into gitcache.RepoPath (repo
  IDENTITY, contrasted with transport-preserving GitURL) and drop cli's
  duplicate cleanRepoPath; repoTail + matchEnvShort now reuse it. Also fixes the
  ssh:// explicit form (drops the leading user@host segment). gitcache test
  added. (round-1 repoTail dup finding resolved here too.)
- tests: wrong-org short handle is rejected; short-handle matching is
  case-insensitive.

Docs: README + env-sync addressing examples already added; update.mdx hint
wording corrected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment thread pkg/gitcache/gitcache.go Outdated
Comment thread pkg/cli/update.go
Comment thread pkg/gitcache/gitcache_extra_test.go
Subagent (round 3, ship):
- envUpdateHint is now a method that appends the "(or short: b update <tail>#)"
  suggestion only when exactly one configured env has that org/repo tail
  (shortHandleUnique); otherwise the short form would itself fail to resolve.
  Both call sites (the ad-hoc-binary note and unknownArgError) updated.
- strengthen TestRunAll_PlanJSON_BinariesOnlyConfig: assert binaries are NOT run
  under --plan-json (not just that output is []).
- new TestEnvUpdateHint_OmitsAmbiguousShortHandle.

Declined: RepoPath keeping a port as a path segment (host:22/...) — ports never
appear in real env keys, and repoTail / right-anchored suffix matching are
unaffected, so it can't cause a mismatch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread pkg/cli/update.go Outdated
Copilot: suggestEnv returned the first env matching the repo tail, so when two
envs share a tail (e.g. github + gitlab mirrors) the "did you mean env" hint
could name the wrong key. Replaced with suggestEnvs (all matches) + an envHint
helper: a single match names the key + copy-paste command; multiple matches list
all candidates and omit the (ambiguous) short handle. Both the ad-hoc-binary
note and unknownArgError use it. Test: HintsAllOnAmbiguousTail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

…// marker

Three Copilot findings on the RepoPath work that an earlier round's timestamp
filter missed (caught by the pre-merge thread-resolution check):

- gitcache.RepoPath mistook a host:port for an scp "host:path" separator after
  an explicit scheme (ssh://host:2222/org/repo, https://host:443/org/repo),
  dropping the host; and trimmed .git before trailing slashes so "repo.git/"
  didn't normalize. Now the scp-colon strip runs only when there was no scheme,
  the leading user@host[:port] segment is dropped for ssh:// forms, and .git is
  stripped after slashes. Tests added for both port and trailing-slash forms.

- the '#' env-marker heuristic also caught git://repo:<filepath> binary refs
  whose in-repo path legally contains '#' (forced env → "unknown env"). git://
  is now exempt from the marker rule (added to hasBinaryProto); git:// envs stay
  addressable by exact/canonical key. Test: GitProtoPathHash_StaysBinary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

@fentas

fentas commented Jun 24, 2026

Copy link
Copy Markdown
Owner Author

Review loop complete — 4 rounds (Copilot + subagent in parallel) + a final verification pass. Findings addressed across 5a5a043, ea84a9a, 47839b4, 1f5a05d, bf6f3b6: env-key round-trip incl. SSH keys, short handles, ambiguity-aware hints, --group/--envs-only/--binaries-only scope, gitcache.RepoPath (ports/.git/), git:// # exemption, --plan-json empty-array fix. 1 false positive refuted, 2 nits declined with rationale. All threads resolved; build/vet/fmt/test green.

@fentas fentas merged commit 8d02543 into main Jun 24, 2026
11 checks passed
@fentas fentas deleted the feat/env-addressing-166 branch June 24, 2026 13:41
fentas added a commit that referenced this pull request Jun 24, 2026
The squash-merge of #167 to main did not fire the push trigger (0 workflow
runs registered for the merge commit), so the 4.18.0 release PR was never
opened. Add a workflow_dispatch trigger as a manual fallback; this commit's
push also re-triggers the workflow. Mirrors e8f917e for the docs deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fentas pushed a commit that referenced this pull request Jun 24, 2026
🤖 I have created a release *beep* *boop*
---


## [4.18.0](v4.17.2...v4.18.0)
(2026-06-24)


### Features

* **update:** address envs by their printed key; add scope flags
([#166](#166))
([#167](#167))
([8d02543](8d02543))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
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.

b update can't scope to a single env — SSH-keyed envs are unaddressable, and --group doesn't gate binaries

2 participants