Repository navigation
Add basecamp connect setup: record trust, write connect.json, check readiness - #731
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Trust-anchor path validation, operator persistence, credential preflight, and readiness reporting contain unresolved correctness and security issues.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds basecamp connect setup to create and validate connector policy for an authenticated agent profile.
Changes:
- Adds credential, identity, operator, ticket, and route checks.
- Introduces validated, atomic
connect.jsonpersistence. - Registers and documents the new command surface.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or rungh pr ready --undo.
Click "Ready for review" or rungh pr readyto reengage.
File summaries
| File | Description |
|---|---|
skills/basecamp/SKILL.md |
Documents connector setup. |
internal/connector/setup/private.go |
Implements private file I/O. |
internal/connector/setup/private_unix.go |
Adds Unix security checks. |
internal/connector/setup/private_unix_test.go |
Tests Unix file protections. |
internal/connector/setup/private_other.go |
Adds non-Unix file handling. |
internal/connector/setup/file.go |
Defines connect.json. |
internal/connector/setup/file_test.go |
Tests schema and validation. |
internal/connector/setup/checks.go |
Implements readiness checks. |
internal/connector/setup/checks_test.go |
Tests readiness checks. |
internal/connector/setup/apply.go |
Applies incremental configuration. |
internal/connector/setup/apply_test.go |
Tests configuration changes. |
internal/commands/doctor.go |
Shares check rendering. |
internal/commands/connect.go |
Implements the new command. |
internal/commands/connect_setup_test.go |
Tests setup workflows. |
internal/commands/commands.go |
Adds command catalog entry. |
internal/commands/commands_test.go |
Registers command in tests. |
internal/cli/root.go |
Registers the root command. |
e2e/smoke/smoke_lifecycle.bats |
Marks interactive setup out of scope. |
.surface |
Records the CLI surface. |
Review details
Suppressed comments (2)
internal/commands/connect.go:403
- An explicit
--deadline 0/0sis silently treated as absent, even though zero is belowMinDeadline; this contradicts the pre-network validation applied to other invalid deadlines. Preserve whether the flag was supplied and reject zero.
if f.deadline != 0 {
internal/commands/connect.go:460
- This login branch runs before checking the existing policy's credential kind and pinned identity. If an Agent-backed
connect.jsonhas been logged out, rerunning with--expect-identityperforms and stores a bot login, then fails the later kind check while claiming nothing changed; a bot policy can similarly consume/store a login for a different identity before the person mismatch is detected. Compareexpectagainstexisting.Agent.Kindandexisting.Agent.IdentityIDbefore launching either ceremony.
case kind == "" && expect != 0:
- Files reviewed: 19/19 changed files
- Comments generated: 6
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
Trust verification, non-Unix privacy enforcement, strict parsing, and result reporting have unresolved correctness issues.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 19/19 changed files
- Comments generated: 5
- Review effort level: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
Machine output, strict parsing, terminal sanitization, and documented error-code behavior have unresolved correctness issues.
Review details
Suppressed comments (4)
Previously missed (2) — in code that hasn't changed since the last review.
internal/commands/connect.go:359
pathincludes the environment-controlled config directory, so it can contain newlines or terminal control sequences. This direct one-line styled write bypasses the sanitization used by the error renderer and lets such a path break or restyle terminal output; quote or sanitize it before rendering.
internal/commands/connect.go:372- The advertised contract says every failed token, identity, operator, scope, ticket, or route check exits with code
not_ready, but this code is limited to post-write failures. Token failures returnauth_requiredat lines 272-273, identity failures return auth errors at 280-286/630-654, and operator failures return usage at 322-323, so automation receives different codes for the documented checks. Preserve the pre-write behavior, but normalize these check failures tonot_ready(or narrow the documented contract).
internal/connector/setup/file.go:248
Decoder.Moreis only a container-element check; after the top-level decode it returns false when the next non-whitespace byte is]or}. Consequently, a valid policy followed by an unmatched closing delimiter is accepted here even thoughadmission.ParsePolicy/json.Unmarshalrejects the same file, breaking the promised parser round-trip. Check that only whitespace remains afterInputOffset(or decode once more and require EOF).
if dec.More() {
return File{}, errors.New("parse connect.json: trailing data after the object")
}
internal/commands/connect.go:352
- This result is constructed only after
setup.Savesucceeds, butWrittenis never set, so every machine-readable success reports"written": falsefor a file that was just written. Set it to true here (and assert it in the JSON-output test).
Checks: summarizeChecks(asDoctorChecks(checks)),
- Files reviewed: 19/19 changed files
- Comments generated: 0 new
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
Credential scope can be misreported and ticket error handling can expose server-returned bearer data.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
internal/commands/connect.go:282
- This scope check reads profile configuration rather than the stored credential that just produced the token. Those values can diverge—for example, reconnecting an existing profile can store an approval downgraded to
readwithout updatingProfileConfig.Scope—so setup can report a read-only connector as ready (or reject a full credential because of stale config). Load the credential's effectiveScopefrom the credential store afterAccessTokenand base readiness on that value.
internal/commands/connect.go:350 pathcomes from the configurable config-directory path and is written directly to a one-line terminal sink. Newlines or terminal escapes in that path can inject or restyle output. Sanitize it withrichtext.SanitizeSingleLinebefore rendering, as the new route-check output already does.
internal/commands/connect.go:673- This generated copy-paste command embeds a config-derived profile name without shell quoting. Profile keys loaded from configuration are not constrained here, so characters such as spaces or shell metacharacters can change the command when pasted. Use
shellQuote(profile), following the established convention ininternal/commands/templates.go:295-300andinternal/commands/files.go:1908-1918.
- Files reviewed: 20/20 changed files
- Comments generated: 1
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
Credential scope can be misclassified, failed setup can retain a mismatched credential, and one styled path output is unsanitized.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
internal/commands/connect.go:350
pathcomes fromXDG_CONFIG_HOME/the home directory and may contain newlines or terminal escape bytes. Writing it directly to this styled terminal sink allows output injection; quote or sanitize it before rendering.
internal/commands/connect.go:283
- The readiness check uses profile configuration rather than the credential that is actually being exercised. Those scopes can diverge: an agent approval/mint can narrow the stored credential to
read, while an existing profile entry is not updated, so a read-only connector can be reported ready (and a full credential can be rejected because of stale config). Re-readCredentials.ScopeafterAccessTokenand base this check on that value.
if scope := profileScope(app, name); scope == "read" {
checks = append(checks, setup.Check{Name: "Scope", Status: setup.StatusFail,
Message: "The credential is read-only: the agent could not reply or acknowledge",
Hint: "Reconnect with full access."})
}
- Files reviewed: 20/20 changed files
- Comments generated: 1
- Review effort level: Balanced
|
🤖 On the two previously-missed comments in Copilot's reviews of 2c8f2ae and 79005d6, both fixed in 689a436:
|
There was a problem hiding this comment.
🟡 Changes recommended
Raw SDK usage violates repository policy, and transaction failure paths can leave or overwrite configuration state.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
internal/commands/connect.go:325
- The child can modify
config.jsonbefore storing the credential: agent connect'sBeforeStorecommits the profile (internal/commands/auth_agent.go:249-280), then the credential write happens afterward (internal/auth/agent.go:648-654). If that write fails, this probe finds no credential, leavestx.connectedfalse, and the outer function skips rollback, so a failed setup leaves the profile registration/binding behind. Track the config mutation independently and restore it on this failure path too.
if held == "" && connectCredentialStored(cmd.Context(), app) {
tx.connected = true
}
- Files reviewed: 20/20 changed files
- Comments generated: 3
- Review effort level: Balanced
…nnect.json Setup runs the agent connection (or the bot-user device login pinned by --expect-identity) by reusing those commands whole, resolves the operator's Person id (from their own profile, an id, or the default profile), writes connect.json owner-only and atomically in admission's own types, and checks token, identity, stream-ticket mint and each routed project's reads as the agent — naming bc3's refusal of those reads to an Agent identity.
…ot ready Review round 1 (Copilot and a separate Opus review): - The operator is verified before connect.json is written, and an operator that fails (a client, an Agent, or an id the agent cannot read) is refused with nothing written. There is no default-profile operator any more: a first setup names one. - The path to connect.json is checked from / down: the config directory, connect/ and the profile directory are this user's alone and no symlinks, and every ancestor (as written and as resolved) belongs to root or this user and is not writable by others unless sticky. On platforms without Unix modes, connect.json must be under the user's profile directory. - A setup lock beside connect.json serialises load, change and save. - A failed check exits non-zero with code not_ready; a read-only credential and a connector with no route are failures, not warnings. - Operator-profile problems, an --expect-identity conflict and a bot login for a missing profile are refused before any browser opens; an unreadable credential store is refused rather than read as no credential. - A typed --concurrency 0 or --deadline 0 is refused; --remove-route is idempotent; account ids and trust mode must be canonical; route paths are sanitised for the terminal.
…efused path An operator already in connect.json was verified when it was recorded, so a re-run that cannot re-read it as the agent (bc3 refuses Agent identities that read) warns and keeps it; one that reads back as an Agent or a client still fails, and a new id is still verified as a new trust anchor. The ancestor refusals name the remedy, and the Windows open comment no longer claims a swap check it cannot make.
Review round 2, fixed as classes: - Trust: setup.VerifyTrust is the single check every setup runs before connect.json is written. It documents and checks each property of the trust anchor, for the operator and every allowlisted id alike: a Person id, never the agent, verified in the account (by the operator's own credential, or a read, which goes through that credential when there is one), not an Agent, not a client, and answered for the id asked. Only a person connect.json already records in the same role may be unreadable, as a warning. - Reporting: setup.Report derives ready from its checks on every call and in its JSON; it cannot be ready with no checks, with nothing written, or with any check whose status is not pass, warn or skip. written is set. - Parsing: duplicate keys at any depth and any trailing data are refused. - Off Unix, where ownership and ACLs are not verified, connect.json is neither read nor written; setup says why instead of trusting the path. - The first help example names the operator.
encoding/json folds field-name case and parses project ids as numbers, so a duplicate check on exact keys missed "Trust" beside "trust" and "048699913" beside "48699913". Every key must now be in its one canonical spelling. The recorded-trust comment says what it assumes, and the tests that need Unix modes build only there.
Review rounds 3 and 4: - Transactional. Every check runs before anything is kept, and connect.json is written only when all of them pass. When the run's own connection or login stored a credential and the run then fails, at any step, the credential is removed and the global config file is restored byte for byte, and the error says to disconnect the agent in Basecamp. - Scope. setup.ScopeCheck is the one classification: only "full" is sufficient (a Launchpad grant, which carries none, is full); read, a missing scope and any value this CLI does not know are insufficient. It reads the scope stored from the server's answer, never the profile's configuration. - Read errors. setup.ErrorText is the one formatter: an HTTP answer is reduced to its status, so no server-written body (a failed ticket mint's included) reaches the checks, the error or the output. - Paths printed to the terminal are sanitised; suggested commands shell-quote profile names.
…ough the SDK
Review round 5 (Copilot and the Opus review):
- The ticket mint goes through the SDK's EventFeed().CreateStreamTicket,
now on main, instead of a raw account-client POST.
- The transaction's invariants are written on setupTransaction and each has
a test that fails without it:
1. it undoes only this run's credential step: the credential it stored,
the profile entry it registered, the account it bound, never the
whole config file, so concurrent changes survive;
2. only while that credential is unproven (token, identity, kind, person,
account, scope): a proven credential is kept when trust, the ticket, a
route or the write fails, so a rerun needs no second approval;
3. a config change is undone even when the credential write itself
failed after the connection committed the entry;
4. connect.json stays one atomic rename after every check passes.
- The undo note names the flow that ran: disconnecting in Basecamp only for
an agent connection.
- The token and identity-endpoint errors go through setup.ErrorText too.
There was a problem hiding this comment.
🟡 Changes recommended
Lock failure classification and lock-proof enforcement do not yet uphold the documented concurrency and security guarantees.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 23/23 changed files
- Comments generated: 4
- Review effort level: Balanced
Review round 12: - auth.HeldCredential is now an interface with a method only the auth package can implement, so no other package can fabricate the proof, and WithCredential invalidates the value when the lock is released: Credentials and Key answer empty afterwards. setup.Save refuses anything but a live one, and it is the only writer of connect.json outside the package's own file-level tests. - Every acquisition outcome is classified in lockFile and nowhere else: held returns the release, held-by-another returns the retryable rate-limit refusal, and a lock that cannot be created at all either warns and proceeds (an ordinary credential operation) or refuses with lock_unavailable (a caller whose guarantee is the lock). There is no path that returns neither a lock nor an error. - The lock-unavailable hint says other commands still work and names connector setup as what needs the lock.
|
🤖 On lock classification and the lock proof (Copilot's review of f6ac37f): both finished structurally in 9660119. Classification. Every acquisition outcome is decided in
Proof. Writers. Tests: TestHeldCredentialIsInvalidAfterTheLockIsReleased, TestSaveRefusesWithoutAHeldCredential, TestWithCredentialRefusesWhenTheHostCannotLock, TestConnectSetupRefusesWhenTheHostCannotLock. |
There was a problem hiding this comment.
🔵 Needs a closer look
Lock failure classification, retry metadata, and cancellation propagation need correction.
Review details
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
internal/commands/connect.go:345
- These helpers turn every request error into a failed check. If the command context is canceled during the ticket mint or a project read, setup therefore returns
not_readyand tells the user to fix readiness instead of propagating cancellation. Checkctx.Err()after each helper and return it before classifying the report.
internal/commands/connect.go:428 busyis transient, but this error leavesRetryablefalse. JSON consumers use that field—not the exit code or hint—to decide whether to retry (internal/output/output_test.go:473-476), so they will treat setup-lock contention as a permanent verdict. SetRetryable: truefor this error.
internal/connector/setup/private.go:66
- If the filesystem rejects
flock(for example, an unsupported network/FUSE mount), this returns a plain error.runConnectSetuptreats every non-ErrSetupRunningerror fromLockasusage, so this path never produces the promisedlock_unavailablecode. Wrap acquisition failures in a distinguishable sentinel/typed error and map that toCodeLockUnavailable.
held, err := lock.TryLock()
if err != nil {
return nil, fmt.Errorf("take the setup lock: %w", err)
- Files reviewed: 23/23 changed files
- Comments generated: 0 new
- Review effort level: Balanced
An unexported method cannot be declared outside its package, but it can be promoted by embedding the interface, and such a wrapper can answer whatever it likes. auth.Held now reads a proof by asking the auth package: anything it did not make is not held, whatever its methods say, and setup.Save uses only that. The doc says what the proof does and does not promise: the lock was held when Save checked it, and the write's own atomicity does the rest.
There was a problem hiding this comment.
🔵 Needs a closer look
Setup-lock acquisition failures are incorrectly classified as usage rather than lock_unavailable.
Review details
Suppressed comments (1)
internal/commands/connect.go:192
- A failure to create or
flockthe per-profile setup lock reaches this branch too, so an unsupported/read-only locking filesystem is currently reported asusage. That contradicts the command'slock_unavailablecontract and prevents callers from distinguishing bad input from a host that cannot provide the required synchronization. Havesetup.Lockwrap acquisition failures in a dedicated sentinel and map that sentinel tooutput.CodeLockUnavailablehere, while retaining privacy/path failures as usage.
return output.ErrUsage(err.Error())
- Files reviewed: 23/23 changed files
- Comments generated: 0 new
- Review effort level: Balanced
The proof's interface now carries nothing but the method that makes it auth's own, so a wrapper has nothing to answer for and no reader can be fooled by one. auth.Held is the only reader, and setup's write and the command's last credential check both go through it.
There was a problem hiding this comment.
🔵 Needs a closer look
Lock classification, identity pinning, retry metadata, and credential-lock proof remain incomplete.
Review details
Suppressed comments (4)
Previously missed (2) — in code that hasn't changed since the last review.
internal/commands/connect.go:324
- On a rerun for a bot profile, an explicit
--expect-identityreplaces the identity pinned in the existing file beforeVerifyAgentruns. If the credential now reports that new identity while/my/profilestill yields the same person ID, setup silently rebindsconnect.json, despite the requirement that a rerun match the recorded agent identity. Compareexpectwithexisting.Agent.IdentityIDand require removing the old file for a deliberate rebind.
internal/commands/connect.go:430 - This transient contention error leaves
Retryablefalse. Machine-output consumers explicitly key onretryablerather than the code (internal/output/envelope.go:59-65), so they will treatbusyas a permanent verdict even though the hint says to retry after the other process finishes.
internal/connector/setup/file.go:380
- The lock key returned by
auth.Heldis discarded, so a live hold for any credential (for exampleprofile:other) authorizes writing this profile's policy. This defeatsSave's documented type-level guarantee that the credential named by the file is the one locked. Require the held key to equal the credential key derived fromf.Profile.
func Save(held auth.HeldCredential, path string, f File) error {
if _, _, ok := auth.Held(held); !ok {
return errors.New("connect.json is written only while the profile's credential is locked")
}
internal/commands/connect.go:192
- When the setup lock file cannot be created or
flockis unsupported,setup.Lockreturns a non-busy error and this branch converts it tousage. That violates the command contract for an unavailable setup lock (lock_unavailable, exit 5); the existing test only exercises the later credential lock. Give setup-lock acquisition failures a distinct sentinel/type and map that toCodeLockUnavailable, while keeping privacy/path validation errors as usage.
if err != nil {
if isLockBusy(err) {
return errBusy(name, err)
}
return output.ErrUsage(err.Error())
- Files reviewed: 23/23 changed files
- Comments generated: 0 new
- Review effort level: Balanced
…locked profile Review rounds 13 and 14 (Copilot): - setup.Lock returns ErrLockUnavailable for a host that cannot lock — a filesystem without flock, a lock file it cannot even inspect — and the command maps it to lock_unavailable. A lock file that is not this user's own regular file is refused before flock sees it, so two setups cannot lock different inodes. - Contention is busy and retryable wherever it comes from, including the credential store's own rate-limited wait, since machine consumers read the retryable field. - setup.Save requires the held lock to be the one for the file's own profile: a lock on another profile's credential is not a lock on this. - A rerun does not rebind connect.json to another identity because --expect-identity says so; that needs the file removed. - A cancelled run reports the interruption rather than a readiness verdict.
|
🤖 On the suppressed comments in the last three reviews, all fixed in db18519:
|
There was a problem hiding this comment.
🟡 Changes recommended
Required credential locks need hardened paths, and final-lock cancellation is currently misclassified as usage.
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/connect.go:411
- Cancellation while waiting for or reading under the final credential lock is reclassified as a usage error here.
WithCredentialreturnscontext.Canceled/context.DeadlineExceededdirectly, butclassifyWriteErrorfalls through toErrUsage, unlike the earlier cancellation check which preserves the context error. Preserve context cancellation before applying write-error classifications so Ctrl-C during lock contention remains an interruption rather than invalid input.
- Files reviewed: 24/24 changed files
- Comments generated: 1
- Review effort level: Balanced
…ption Review round 13 (Copilot): - A required credential lock now refuses a lock directory or lock file this user does not own: a symlink, another user's file, or one others can write. A lock nobody can be sure names the same inode is not a lock, so it reports lock_unavailable rather than being used. Ordinary locks are unchanged. - Cancellation under the final lock stays an interruption: a context that ended is returned as it is, not classified as invalid input. - ProfileCredentialKey has its own doc, and the busy arm its own return.
|
🤖 On the suppressed comment (connect.go:411, cancellation under the final lock classified as usage): fixed in b526e73. classifyWriteError returns a context.Canceled or context.DeadlineExceeded as it is, before any other classification, so Ctrl-C while waiting for the credential lock stays an interruption and nothing is written. TestConnectSetupClassifiesCancellationUnderTheLock covers both, red on db18519. |
There was a problem hiding this comment.
🟢 Approved
The implementation and extensive tests consistently enforce the stated identity, readiness, confidentiality, and locking guarantees.
Review details
- Files reviewed: 26/26 changed files
- Comments generated: 0 new
- Review effort level: Balanced
The connector has admission, which reads trust and routes from
connect.json, and a way to connect an agent (basecamp auth agent connect). Nothing writesconnect.json, and nothing checks that a connected profile can actually run a connector. Today that means hand-writing the trust anchor and finding out at run time that Basecamp refuses the agent's reads.Tracked in 16 basecamp connect setup.
Why
connect.jsonis the only authority for who may drive the agent and which directory a project's work runs in. It has to come from something that verifies the identities in it and refuses a copy someone else could have edited. It is also wherebasecamp connectstarts, so this adds the command group.Scope: setup on a connected profile
basecamp connect setup -P <profile>works on a profile that already holds the agent's credential. It never obtains, changes or removes a credential. A profile with none is refused, and the refusal names the command that connects it:A first version also ran the connection itself and undid it on failure. Six review rounds kept finding the same kinds of problem there: locks released before the undo, a concurrent login's credential deleted by key, prose and a JSON envelope sharing stdout, and a stale account after the child step. A design pass over the invariants split it out. Setup running the credential step follows in its own PR, once the credential store can compare-and-delete what a run stored.
What changes
Account. The account is the one the agent's profile is bound to. It is never taken from a config-wide default, and an explicit
--accountnaming another account is refused.Identity. An Agent credential must read back as an Agent person with a positive id. A bot login (
--expect-identity, v1's path) must be the pinned identity. On a rerun, the agent and account must match whatconnect.jsonrecords.Trust. Keyed on Person id, and never guessed.
--operator-profile, whose credential proves who they are) or by--operator <id>.--allowid go through one check before anything is written: a person in the account, not the agent, not an Agent, not a client. Person reads go through the operator's credential when there is one, since Basecamp refuses them to an Agent today.connect.jsonalready records it in the same role.connect.json lives at
<config>/connect/<profile>/connect.json. Trust and routes are written in admission's own types, and every write is re-parsed throughadmission.ParsePolicy+Validate.watch_completionsper route, class, driver (spawn), concurrency (2), deadline (45m), worktrees, and the agent's person id, kind and canonical account id.--remove-route,--no-watch-completions,--class <id>=,--trust operator,--worktrees=false.Private file.
connect.jsonis written 0600 in 0700 directories, atomically, under a per-profile setup lock. It is refused on read, and never replaced on write, in these cases:connect/or the profile directory is a symlink, not regular, owned by another user, or writable by group or others;/, as written or as its symlinks resolve, belongs to someone other than root or this user, or is writable by others without being sticky.Where ownership can't be verified (Windows), setup refuses. Unknown, duplicate or non-canonical keys are refused, and so is trailing data.
Locking. The last look at the credential and the write are one step under that credential key's own cross-process lock, which every login, refresh, import and logout already takes, so no command can replace the profile's credential between them.
setup.Savetakes the held-lock value, so a write without the lock does not compile. Where the configuration directory's filesystem cannot lock at all — no writable lock directory, no flock — setup refuses withlock_unavailableand changes nothing, rather than running unsynchronized as an ordinary credential operation would. Another setup or another command holding the profile is reported asbusy; both exit with the rate-limit status.Bound to the identity.
connect.jsonrecords the agent's person id, kind and pinned identity, andFile.VerifyAgentrefuses a file that does not describe the identity a profile's credential authenticates as. Setup runs it inside the locked section; the connector runs it at start-up, so a credential replaced afterwards stops the connector instead of letting it act as the wrong agent.Checks, run as the agent: token, identity, trust, scope (only a granted
fullpasses), stream-ticket mint, and each routed project's project and project-people reads.connect.jsonis written only when every check passes.Exit status.
usage: refused input.auth: no credential, an unreadable one, or one that isn't the agent it should be. Every credential conflict names the command that fixes it.busy/lock_unavailable: another command holds the profile, or the host cannot lock. Nothing was changed.not_ready: a readiness check failed, and nothing was written.Credential handling
Setup holds no secret, and
connect.jsonholds no credential. The stream ticket is minted through the SDK'sEventFeed().CreateStreamTicketand dropped at once. A failed read is reported by HTTP status only, so no server-written body reaches output, including a failed mint's. Setup's reads go through an SDK client with no request hooks, debug logger or cache, so-vcannot print a feed URL. Setup builds no feed connection and no poll source.Merge order
No
go.mod, vendored model or Nix hash change. The instance lock and ledger in Take the account event feed into a durable ledger are keyed on account and agent Person id.connect.jsonrecords both, with the account in the same canonical spelling.