Skip to content

Point the account-binding hint at a command that exists, and make the check able to say so - #744

Merged
jorgemanrubia merged 6 commits into
mainfrom
connect-hint-drift-exact
Sep 17, 2026
Merged

jorgemanrubia merged 6 commits into
mainfrom
connect-hint-drift-exact

Conversation

@jorgemanrubia

@jorgemanrubia jorgemanrubia commented Sep 17, 2026 •

Copy link
Copy Markdown
Member

An operator whose connector profile has no account was stopped by basecamp connect setup with Bind it: basecamp profile set <name> account_id <id>. That is not a command — profile has no set — so the one instruction given at the moment of being stuck fails with "unknown command".

Originally tracked in connect.go points at a command that doesn't exist, and drift check can't catch it. The hint arrived with Add basecamp connect setup: record trust, write connect.json, check readiness and was spotted while building Let an agent connect an agent and manage connector setup: the basecamp-connect skill.

There is no command that binds an account on its own. The account is written by a command that stores a credential: basecamp auth agent connect -P <name> takes the Agent's own account, and the headless logins (auth login --with-token / --with-client-credentials with --account) take the one named. A browser login binds nothing. So the hint now names what actually works for the profile in front of it: connecting the Agent, or — for a bot user that already logged in through the browser — adding account_id to its entry in the global config. A profile whose accountless entry comes from a repo or local config gets only the config-file remedy, because every binding command refuses it. An account_id that is set but unusable is now reported as that, not as "unbound".

The second half is why it survived review. The skill drift check resolved a referenced command to its nearest existing ancestor, so basecamp profile set passed as basecamp profile. Resolution is now exact. Words past a command still pass where they cannot name a subcommand — argument values, and prose after a leaf — and a word naming a command recorded in .surface-breaking fails first, which is what lets removal be caught at all. A word that runs on into a filename or key is read as the argument it is. The check passes on all three skills, and its behavior is held by fixture tests that fail against the old script.

Hints written in Go are not skill files, so they get a test of their own against the real command tree rather than .surface: the commands named in hints across internal/commands and the connector's readiness checks — written inline, built into a variable, returned by a helper, passed through a wrapper, or assigned to a hint field — plus every string in connect.go. It asks a group's own argument validator whether a word can follow it, so it is exact where the script can only approximate, and it fails on the old hint. A pass over the tree found one other reference to a command that doesn't exist, in a comment, which is corrected.

What the checks still cannot see is documented in AGENTS.md: a subcommand that never existed, under a command that also takes an argument, reads as the argument.

… check able to say so

A profile that holds a credential but no account stopped connector setup
with "Bind it: basecamp profile set <name> account_id <id>". There is no
`profile set`. The account is bound by the command that stores the
credential, so the hint now names those: `basecamp auth agent connect -P
<name>`, which takes the agent's own account, and `basecamp auth login -P
<name> --account <id>` on the bot-user path.

The skill drift check could not catch it. It resolved a reference to the
nearest existing ancestor, so `basecamp profile set` resolved as `basecamp
profile` and passed. A reference now has to resolve exactly. Words past a
resolved command are still allowed where they cannot name a subcommand —
after a command that takes positional arguments, and after a leaf that has
none — which is what the fallback was really covering: argument values and
prose. What is left is a group that only dispatches followed by a word it
does not dispatch to, and that fails.

Hints in Go code are not skill files, so they get their own test:
TestHintCommandsResolve resolves every command named in a hint across
internal/commands, and every command named in any string in connect.go,
against the real command tree. It is red on the old hint.
Copilot AI balanced review requested due to automatic review settings September 17, 2026 08:31
@github-actions github-actions Bot added commands CLI command implementations tests Tests (unit and e2e) ci CI/CD workflows docs labels Sep 17, 2026
@github-actions

Copy link
Copy Markdown

Sensitive Change Detection (shadow mode)

This PR modifies control-plane files:

  • .github/workflows/test.yml

Shadow mode — this check is informational only. When activated, changes to these paths will require approval from a maintainer.

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.

🟡 Changes recommended

Ambiguous command references and variable-built hints can still evade validation, and the replacement hint fails for layered profiles.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Corrects the account-binding hint and adds validation to prevent nonexistent CLI commands from appearing in skills and hints.

Changes:

  • Replaces the invalid profile set remedy.
  • Adds exact command-resolution checks and fixtures.
  • Integrates drift tests into CI and updates documentation.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

File summaries
File Description
scripts/test-skill-drift.sh Adds drift-check fixtures.
scripts/check-skill-drift.sh Tightens command resolution.
Makefile Registers the new test target.
internal/connector/ledger_recovery.go Corrects an inaccurate comment.
internal/commands/hint_commands_test.go Validates commands referenced by hints.
internal/commands/connect.go Replaces the invalid account-binding hint.
AGENTS.md Documents drift and hint validation.
.surface-skill-drift Documents command baselines.
.github/workflows/test.yml Runs drift fixtures in CI.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 3
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/commands/connect.go Outdated
Comment thread internal/commands/hint_commands_test.go Outdated
Comment thread scripts/check-skill-drift.sh
…moved subcommands

Three holes Copilot found in the first pass.

The replacement hint named commands that refuse the very profile it fires
for: both login paths turn away an accountless entry that is not the global
config's, because the entry they would write stays shadowed by the layer
that defines it. That profile now gets the only remedy it has — the config
file the entry comes from. And an entry naming an account nothing can parse
is no longer called unbound: the account it does name is what is wrong, and
that is what it says.

The hint test read only literals written where the hint is passed, so a
hint built into a variable first (boost.go builds one, then appends --event)
was invisible to it. It now follows the names a hint expression mentions.

Exact resolution still could not tell an argument value from a subcommand we
dropped: `basecamp recordings archive` reads as the [type] argument the
moment `archive` is removed. Both checks now refuse a word that names a
command in .surface-breaking before either escape can read it as prose —
which is what makes the claim that removal is caught true.

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.

🟡 Changes recommended

Layered-profile handling, helper-returned hint coverage, and Bash compatibility remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

scripts/test-skill-drift.sh:89

  • ${parameter@Q} requires Bash 4.4, while this repository's existing script gate supports Bash 4+ (scripts/check-smoke-coverage.sh:14-18). On Bash 4.0–4.3, make test-skill-drift exits with bad substitution precisely when an assertion mismatch should be reported. Use a portable quoted message (for example, expected '$2') or explicitly raise and enforce the minimum Bash version.
  • Files reviewed: 10/10 changed files
  • Comments generated: 2
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.

Comment thread internal/commands/connect.go Outdated
Comment thread internal/commands/hint_commands_test.go Outdated
The hint still named `basecamp auth login -P <name> --account <id>` for the
bot-user path. A browser login binds no account — only the headless
--with-token and --with-client-credentials paths do — so the operator would
log in, succeed, and hit the same refusal. The hint now names what binds:
connecting an Agent takes its own account, and a bot's account goes into its
entry in the global config. An unusable account_id now says which profile
names it and where to correct it.

The hint test missed hints assigned to a field (doctor.go's check.Hint),
hint-named fields (feed.go), hints returned by a helper (wizard.go), and the
readiness checks in internal/connector/setup whose hints connect setup
shows. It reads all of those now, asserts one known reference per kind so a
collector that goes blind fails, and asks a group's own argument validator
whether a word past it is an argument — so `basecamp setup agentz` fails,
as the CLI would.

The drift check read "basecamp upload report.pdf" as the removed `upload
report`, and "config set project_id" as `config set project`: a word running
on into a filename or key is an argument, and extraction now stops before
it. A baselined command's flags are checked again, against the deepest
command that exists, and every DRIFT line names the baseline entry that
acknowledges it.
Copilot AI previously approved these changes Sep 17, 2026

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.

🟢 Approved

The updated hints are actionable, and the new focused checks cover the command-resolution regression without unresolved issues.

Review details
  • Files reviewed: 10/10 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.

…s nothing

The root runs and has no argument validator, which cobra's ValidateArgs
reads as "accepts anything" — so `basecamp profilez set` in a hint passed,
though the CLI answers "unknown command". The root now takes no word it
does not know.

"A bot user's login binds none" was too broad: a headless --with-token login
with --account does bind. It is the browser login that binds nothing, and
that is what the hint and its comment now say.

The scan followed every name a hint expression mentioned, so `cmd` in
cmd.CommandPath() pulled whole command definitions, help text included, in
as hints. Only names that are values are followed now.
@jorgemanrubia
jorgemanrubia requested a balanced review from Copilot September 17, 2026 09:18
Copilot AI dismissed their stale review, a newer Copilot review was requested September 17, 2026 09:18

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.

🟡 Changes recommended

The hint scanner misses wrapper-forwarded hints, and the fixture harness contains a Bash 4.4-only expansion.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

internal/commands/hint_commands_test.go:205

  • This sink detection still misses hints passed through local wrapper functions. For example, upgrade_selfupdate.go:324 passes "Re-run basecamp upgrade" to errUpgradeFailedHint; that helper assigns its hint parameter to output.Error.Hint, but this scan neither marks the wrapper argument as hint text nor traces callers backward, so changing that command to a nonexistent path leaves this test green. Please propagate arguments through local calls whose parameters reach a Hint field (or register those wrappers as sinks) and cover errUpgradeFailedHint with a scanner fixture.
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.

Comment thread scripts/test-skill-drift.sh Outdated
A hint passed to a local wrapper — errUpgradeFailedHint(msg, "Re-run
basecamp upgrade ...") — was invisible: the scan knew only the output
constructors as places hint text goes. Every package function whose
parameter reaches a hint inside it is now one too, and so is a wrapper of
one, found by running until no function gains a parameter.

A parameter or name counts only when it is the text itself: the whole
expression, or a side of a concatenation. Following names a hint is merely
formatted from ran from formatCardTableIDs(cardTables) to every `cmd :=
&cobra.Command{...}` in the file, so help text was scanned as hints — and
with the root now refusing unknown words, "the way basecamp does it" in a
Short would have failed the test. A function literal called in place, and
the call a method chain starts from, are read.

The fixture harness used ${2@Q}, which bash 3.2 does not have; an
expected-substring mismatch there aborted the run instead of reporting it.
Copilot AI previously approved these changes Sep 17, 2026

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.

🟢 Approved

The updated implementation addresses prior findings with focused regression coverage and no remaining correctness issues.

Review details
  • Files reviewed: 10/10 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.

…n does

Reading a function literal called in place and the receiver of a method
chain changed nothing: no hint in either package takes either shape, the
scanned set is identical without them, and no tripwire could notice them
regress. They are gone.

The package function index is keyed by bare name, so a collision narrows
the scan — the shadowed declaration is never followed or read as a wrapper —
rather than widening it as the comment said. No colliding name carries a
hint today; the comment now says both.
@jorgemanrubia
jorgemanrubia requested a balanced review from Copilot September 17, 2026 09:45
Copilot AI dismissed their stale review, a newer Copilot review was requested September 17, 2026 09:45

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.

🟢 Approved

The revised hints are actionable, command validation is substantially strengthened, and regression coverage addresses the identified failure modes.

Review details
  • Files reviewed: 10/10 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.

@jorgemanrubia
jorgemanrubia merged commit 111256e into main Sep 17, 2026
26 checks passed
@jorgemanrubia
jorgemanrubia deleted the connect-hint-drift-exact branch September 17, 2026 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI/CD workflows commands CLI command implementations docs tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants