Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .claude/commands/c1i.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ rejected rather than rewritten. A bare `mycompany` is rejected as ambiguous.
- `--debug` / `C1I_DEBUG` — trace HTTP method/URL/status/timing to stderr
(never headers or bodies).

`--debug` and `--max-retries` take effect only on the paths built on the shared
transport (REST, `mcp gateway`, `auth login`). They are inert on the `docs`
subcommands that fetch — `docs search`, `docs page`, `docs openapi`,
`docs endpoints`, `docs endpoint` — which issue their own HTTP, so no trace
there does not mean no request was sent.

## Errors & exit codes

| Code | Meaning |
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ dist/
# whose parent directory is excluded, so dev/README.md would vanish too.
dev/*
!dev/README.md

# Agent worktrees: `git worktree add` targets land here. Untracked, they stamp
# every local build "+dirty", and a `git add -A` records one as a gitlink
# (mode 160000) -- an embedded-repo pointer no clone of this repo can resolve.
.claude/worktrees/
79 changes: 79 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,79 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Fixed

- **Flags that shipped undocumented are now documented, and a guard keeps it
that way.** Six flags were reachable from `--help` but named in neither
README.md nor `cmd/agents.md`: `apps set-owners --wait`/`--wait-timeout`,
`auth token --json`, `mcp servers register --token-sharing`/
`--source-app-id`, and `completion --no-descriptions`. An agent that can't
find a flag rebuilds it by hand -- a hand-rolled pagination loop where
`--paginate` would have done, a hand-rolled MCP registration where
`--tool-prefix` would have. All six are now documented.
`cmd/agents.md` also gained a "Global flags" section: the
agent-facing index named only three of the six persistent flags, leaving
`--debug`, `--max-retries`, and `--error-format` discoverable from the
README alone. It now also points at `--list-key` and `--allow-delete-body`
next to the `c1i api` conventions they apply to.

`TestEveryFlagIsDocumented` (`cmd/flag_docs_coverage_test.go`) fails CI on
any long flag absent from both docs, and
`TestGlobalFlagsDocumentedInAgentsDoc` holds the persistent flags to both.
Matching is boundary-anchored, so `--wait` is not satisfied by a doc that
only mentions `--wait-timeout`. The exemption map is empty.

- **`cmd/agents.md` no longer tells agents that `mcp gateway` ignores
`--debug`/`--max-retries` or follows redirects unguarded.** Both were false.
`cmd/mcp_gateway.go` threads both flags into the gateway's bearer mint and
its JSON-RPC calls, and `mcpgateway` is built on the shared
`internal/transport`, which applies the empty-path and redirect guards
unconditionally. The cost of the first one was concrete: an agent debugging a
hanging `mcp gateway call` would read the doc and never try the one flag that
shows where it stopped. `CLAUDE.md`'s "both are currently silently inert on
the packages that issue their own HTTP" was the stale source of the claim and
is corrected too.

- **`auth token`'s help no longer claims the token is "audience-scoped to the
C1 API host".** The CLI neither requests nor observes that: the token request
sends only `client_id`, `grant_type`, `client_assertion_type` and
`client_assertion` (no `audience`, no `resource`), and nothing decodes the
returned token. The only `aud` in the tree is on the client-assertion JWT
sent *to* the token endpoint. Dropped from the README too.

- **The `--debug`/`--max-retries` scope exception is documented in all four
docs, and guarded.** The fetching `docs` subcommands (`docs search`,
`docs page`, `docs openapi`, `docs endpoints`, `docs endpoint`) call
`http.DefaultClient` directly instead of `internal/transport`, so both flags
are inert there and no path or redirect guard applies. README.md, CLAUDE.md
and `.claude/commands/c1i.md` all stated the opposite as an unqualified
universal -- README's was the strongest ("everywhere the CLI sends HTTP").
An agent debugging an empty `docs search` would run `--debug`, see no trace,
and conclude no request was sent. `cmd/agents.md` also now records that these
five don't share one host: three fetch `conductorone.com` (cached 24h, so a
run can return rows without sending a request at all) while `docs search` and
`docs page` call a third party, `api.mintlify.com`.

`TestFlagScopeExceptionDocumented` fails CI when any of the four documents
mentions either flag without carving out the exception;
`TestDocumentedFlagScopeExceptionIsStillReal` pins the exception to the code,
failing in both directions -- if a file starts sending HTTP outside
`internal/transport`, or if these stop and the carve-outs go stale. It parses
the AST rather than grepping one spelling, so `http.Get`/`Post`/`Head`/
`PostForm` and an `http.Client` constructed any idiomatic way (composite
literal, `new()`, a value var or struct field) all count, while a
`*http.Client` parameter correctly does not. It walks the whole repo rather
than `cmd/` alone -- `internal/` is the likelier home for the next one --
skipping `.claude/`, since the worktrees gitignored above would otherwise
make it fail locally while passing in CI.

- **`apps create` and `apps delete` are in the README.** Both shipped without
ever being listed in the Apps section, so the only way to find them was
`--help`. Documented alongside their neighbours, including that `apps create`
auto-assigns the caller as an owner and returns the new app under an `app`
key, and that `apps delete` marks the app with `deletedAt` rather than
erasing it. `apps delete`'s own help text claimed its endpoint was absent
from the OpenAPI spec; it is published (`DELETE /api/v1/apps/{id}`, operation
`c1.api.app.v1.Apps.Delete`), so that sentence is gone from the help too.

- **`apps set-owners` no longer claims new owners appear in `apps get`'s
`appOwners` field.** Measured against a live tenant, `appOwners` was empty
on every app checked -- all 47, spanning 45 connector-managed apps across
Expand All @@ -37,6 +110,12 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
reads that do work (`apps owners`, added above; `ownerids` in the
`set-owners` help, which is the read `--wait` polls).

- **`.claude/worktrees/` is gitignored.** Agent worktrees land there, and
untracked they stamped every local build `+dirty` -- a string that reaches
the wire in the user-agent and the MCP gateway handshake's
`clientInfo.version` -- and a `git add -A` would have committed one as a
gitlink (mode 160000), an embedded-repo pointer no clone can resolve.

## [0.5.0] - 2026-08-21

### Added
Expand Down
54 changes: 40 additions & 14 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ tracked root file outside its allowlist. Stage explicit paths rather than `-A`.
`--max-retries` / `C1I_MAX_RETRIES` (default `client.DefaultMaxRetries`),
`--error-format` / `C1I_ERROR_FORMAT` (`text`|`json`), `--dry-run` /
`C1I_DRY_RUN` (preview a mutating request without sending it), `--debug` /
`C1I_DEBUG` (trace HTTP requests to stderr). See README for behavior.
`C1I_DEBUG` (trace HTTP requests to stderr). `--debug` and `--max-retries`
are inert on the fetching `docs` subcommands (`docs search`, `docs page`,
`docs openapi`, `docs endpoints`, `docs endpoint`) — see README for behavior.

### Patterns to follow when adding/changing commands

Expand Down Expand Up @@ -135,6 +137,15 @@ tracked root file outside its allowlist. Stage explicit paths rather than `-A`.
accepts 0 and the documented count; a command documenting none must not
require one. Rejecting a *stray* positional is the guard's job, not the
test's.
- **A new flag must be named in a top-level doc (enforced convention):**
`TestEveryFlagIsDocumented` (`cmd/flag_docs_coverage_test.go`) walks the tree
and fails CI on any long flag absent from **both** README.md and
`cmd/agents.md`; `TestGlobalFlagsDocumentedInAgentsDoc` holds rootCmd's
persistent flags to both docs, since they apply everywhere. This exists
because agents rebuild by hand what a flag already does when no doc names it.
Exemptions go in `flagDocExemptions` with a reason (persistent flags use the
separate `globalFlagDocExemptions`, which the second test reads) —
documenting the flag in one line is almost always the better fix.
- **API client:** build it with `newClient(cmd, baseURL)` (cmd/client.go), not
`client.New` directly — the helper threads the global flags (retries, etc.).
- **Paths:** interpolate IDs into request paths with `client.Path("…/%s", id)`,
Expand Down Expand Up @@ -223,19 +234,34 @@ a package, verify each of these against the new code:
failed" distinct from "C1 failed".
- **Escape ids in paths** (`client.Path`-style), use the shared output helpers in
`cmd`, and honor the global flags where applicable.
- **Reject an empty id before sending, and don't trust a 3xx either.** The
shared client refuses a path with an empty segment (`client.PathError` →
exit 2) and refuses to follow a redirect whose target path differs from
the request's (`client.RedirectError` → exit 2) — a package that issues
its own HTTP inherits neither: `cobra.ExactArgs` will hand you `""`
happily, and Go's default `http.Client` follows a 3xx transparently. An id
of `""`, `"/"`, or `"."` reaching the collection endpoint by either path is
the shape that produced the silent "returned the whole list with exit 0"
bug.
- **Honor `--debug` and `--max-retries`.** Both are documented as global, and
both are currently silently inert on the packages that issue their own HTTP —
so tracing shows nothing and transient failures aren't retried on those paths.
Don't add a fourth.
- **Build on `internal/transport` (or `internal/client`, which wraps it).**
Two things come free: `transport.New` applies the empty-path guard
(`client.PathError` → exit 2) and the redirect guard (`client.RedirectError`
→ exit 2) to every client it builds. The global flags do **not** come free —
see the next-but-one bullet. Every `internal/` package that issues HTTP is
built this way, `internal/mcpgateway` and `internal/login` included.
- **Reject an empty id before sending, and don't trust a 3xx either.** A
package that hand-rolls its own `http.Client` gets neither guard:
`cobra.ExactArgs` will hand you `""` happily, and Go's default
`http.Client` follows a 3xx transparently. An id of `""`, `"/"`, or `"."`
reaching the collection endpoint by either path is the shape that produced
the silent "returned the whole list with exit 0" bug.
- **Thread `--debug` and `--max-retries` yourself — nothing does it for you.**
`transport.New` *accepts* both as options but sources neither: it defaults to
`DefaultMaxRetries` and debug-off and reads nothing ambient, so
`transport.New(base)` with no options ships tracing that shows nothing and a
retry count the flag can't change. Every caller passes them by hand —
`cmd/client.go:15-16` (the single viper read every REST command inherits via
`newClient`), `internal/client` from its own config, and `cmd/mcp_gateway.go`,
`cmd/auth_login.go`, `cmd/auth_token.go` from viper. The deliberate
exception is `auth login`'s device-flow polling leg, where `PollForToken`
forces `WithMaxRetries(0)`: the RFC 8628 poll interval is that call's retry
strategy. The accidental one is the fetching `docs` subcommands —
`docs search`, `docs page` (`cmd/docs_search.go`) and `docs openapi`,
`docs endpoints`, `docs endpoint` (`cmd/docs_openapi.go`) — which call
`http.DefaultClient.Do` directly: both flags are inert at those three call
sites, no path or redirect guard applies, and they return bare `fmt.Errorf`
rather than a classifiable error. Don't add a fourth.

When implementing a wire protocol or stream parser (JSON-RPC, SSE, MCP, …), code
and test against the **full input space the spec permits**, not just the shape a
Expand Down
Loading
Loading