Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
665e431
feat: better error message if scheme forgotten in CLI `*_BASE_URL`/`-…
stainless-app[bot] Apr 3, 2026
40329ac
feat: allow `-` as value representing stdin to binary-only file param…
stainless-app[bot] Apr 3, 2026
2f49e45
chore: switch some CLI Go tests from `os.Chdir` to `t.Chdir`
stainless-app[bot] Apr 3, 2026
5fc2402
chore: mark all CLI-related tests in Go with `t.Parallel()`
stainless-app[bot] Apr 3, 2026
07389a5
feat(api): api update
stainless-app[bot] Apr 3, 2026
b7fd553
chore: modify CLI tests to inject stdout so mutating `os.Stdout` isn'…
stainless-app[bot] Apr 4, 2026
28b2498
fix: fall back to main branch if linking fails in CI
stainless-app[bot] Apr 7, 2026
d4f3537
fix: fix quoting typo
stainless-app[bot] Apr 7, 2026
2f69100
feat(api): api update
stainless-app[bot] Apr 8, 2026
61bdeb0
codegen metadata
stainless-app[bot] Apr 8, 2026
78f8725
codegen metadata
stainless-app[bot] Apr 9, 2026
d9c0c77
chore(cli): let `--format raw` be used in conjunction with `--transform`
stainless-app[bot] Apr 9, 2026
5784e71
chore(cli): additional test cases for `ShowJSONIterator`
stainless-app[bot] Apr 10, 2026
ad3cbf1
fix: fix for failing to drop invalid module replace in link script
stainless-app[bot] Apr 10, 2026
a95a486
fix(cli): fix incompatible Go types for flag generated as array of maps
stainless-app[bot] Apr 10, 2026
62e382b
docs: update examples
stainless-app[bot] Apr 11, 2026
2586047
chore: add documentation for ./scripts/link
stainless-app[bot] Apr 14, 2026
1b5c7e9
chore(cli): fall back to JSON when using default "explore" with non-TTY
stainless-app[bot] Apr 15, 2026
fc1ebc7
feat(cli): alias parameters in data with `x-stainless-cli-data-alias`
stainless-app[bot] Apr 15, 2026
4a9e250
chore(cli): switch long lists of positional args over to param structs
stainless-app[bot] Apr 16, 2026
05fc238
feat(api): api update
stainless-app[bot] Apr 16, 2026
b5836aa
chore(ci): support manually triggering release workflow
stainless-app[bot] Apr 17, 2026
4b76c79
feat(cli): send filename and content type when reading input from files
stainless-app[bot] Apr 17, 2026
4222549
feat(cli): add `--raw-output`/`-r` option to print raw (non-JSON) str…
stainless-app[bot] Apr 17, 2026
6ea47ba
feat(api): api update
stainless-app[bot] Apr 18, 2026
9e0c109
chore(cli): use `ShowJSONOpts` as argument to `formatJSON` instead of…
stainless-app[bot] Apr 18, 2026
522092e
chore(tests): bump steady to v0.22.1
stainless-app[bot] Apr 18, 2026
e08b27e
codegen metadata
stainless-app[bot] Apr 22, 2026
7ee8632
codegen metadata
stainless-app[bot] Apr 22, 2026
878ee7b
codegen metadata
stainless-app[bot] Apr 22, 2026
7bd511f
codegen metadata
stainless-app[bot] Apr 22, 2026
1fb3c19
codegen metadata
stainless-app[bot] Apr 22, 2026
86e522e
codegen metadata
stainless-app[bot] Apr 22, 2026
014bf27
chore(internal): more robust bootstrap script
stainless-app[bot] Apr 23, 2026
f752384
codegen metadata
stainless-app[bot] Apr 23, 2026
317503c
codegen metadata
stainless-app[bot] Apr 24, 2026
f8df59b
codegen metadata
stainless-app[bot] Apr 25, 2026
9a73f60
codegen metadata
stainless-app[bot] Apr 25, 2026
ca5609b
codegen metadata
stainless-app[bot] Apr 25, 2026
15efdbc
feat(api): api update
stainless-app[bot] Apr 26, 2026
75c6ac1
codegen metadata
stainless-app[bot] Apr 27, 2026
4b771ee
codegen metadata
stainless-app[bot] Apr 28, 2026
357c382
codegen metadata
stainless-app[bot] Apr 29, 2026
c419250
fix(cli): correctly load zsh autocompletion
stainless-app[bot] Apr 30, 2026
fb3478d
codegen metadata
stainless-app[bot] Apr 30, 2026
c983439
fix: flags for nullable body scalar fields are strictly typed
stainless-app[bot] Apr 30, 2026
14f99ef
feat: support passing path and query params over stdin
stainless-app[bot] Apr 30, 2026
95294ce
codegen metadata
stainless-app[bot] May 1, 2026
c472cd1
codegen metadata
stainless-app[bot] May 1, 2026
cde57e6
feat(api): api update
stainless-app[bot] May 5, 2026
9eee0f2
release: 0.2.0
stainless-app[bot] May 5, 2026
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
1 change: 1 addition & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
push:
tags:
- "v*"
workflow_dispatch: {}
jobs:
goreleaser:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.1.0"
".": "0.2.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 30
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-7d29d0843a52840291678a3c6d136f496ae1f956853abaa5003c1284ca2c94aa.yml
openapi_spec_hash: 010597ad0ec6376fbf2f01ec062787ad
config_hash: bd8505e17db740d82e578d0edaa9bfe0
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell/hyperspell-3ea7819d73f46347d7870c6238e12458921045b6386d6091bffe67906d7a017f.yml
openapi_spec_hash: 004b15cbe7b318ef25b91f6d45b4cba3
config_hash: 597eba5e5eaec83a5f0db3d946af8db5
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,55 @@
# Changelog

## 0.2.0 (2026-05-05)

Full Changelog: [v0.1.0...v0.2.0](https://github.com/hyperspell/hyperspell-cli/compare/v0.1.0...v0.2.0)

### Features

* allow `-` as value representing stdin to binary-only file parameters in CLIs ([40329ac](https://github.com/hyperspell/hyperspell-cli/commit/40329ac0504bec769cdcbd0eda4821b15cf164d1))
* **api:** api update ([cde57e6](https://github.com/hyperspell/hyperspell-cli/commit/cde57e6cc0907e80bc2fa80d05b6701c3b777863))
* **api:** api update ([15efdbc](https://github.com/hyperspell/hyperspell-cli/commit/15efdbcd4ea5962464fe5eedc212d0fd601ceb1f))
* **api:** api update ([6ea47ba](https://github.com/hyperspell/hyperspell-cli/commit/6ea47ba9a0248351256a8ddd0bde7b5575a53eb3))
* **api:** api update ([05fc238](https://github.com/hyperspell/hyperspell-cli/commit/05fc238a5087e6cd3024fd92eccb7fa23cbb8440))
* **api:** api update ([2f69100](https://github.com/hyperspell/hyperspell-cli/commit/2f691007ba68d3b18b6a643db158ed16b4848871))
* **api:** api update ([07389a5](https://github.com/hyperspell/hyperspell-cli/commit/07389a55c46d676894d6392d7c6ed4cf33ac4147))
* better error message if scheme forgotten in CLI `*_BASE_URL`/`--base-url` ([665e431](https://github.com/hyperspell/hyperspell-cli/commit/665e431d3c39a2ba149168250d080bdf8b7546b5))
* **cli:** add `--raw-output`/`-r` option to print raw (non-JSON) strings ([4222549](https://github.com/hyperspell/hyperspell-cli/commit/422254937af57c228a18d67191aa8966cceebb45))
* **cli:** alias parameters in data with `x-stainless-cli-data-alias` ([fc1ebc7](https://github.com/hyperspell/hyperspell-cli/commit/fc1ebc74cf40a58ddb979128852ed202354922cc))
* **cli:** send filename and content type when reading input from files ([4b76c79](https://github.com/hyperspell/hyperspell-cli/commit/4b76c79a99e8bb0f8a3ef112ccaf3092decb69a3))
* support passing path and query params over stdin ([14f99ef](https://github.com/hyperspell/hyperspell-cli/commit/14f99ef63dff6a55aca07c84419c42ce25d99142))


### Bug Fixes

* **cli:** correctly load zsh autocompletion ([c419250](https://github.com/hyperspell/hyperspell-cli/commit/c419250da2b4345806d79c322c0384742e8b234a))
* **cli:** fix incompatible Go types for flag generated as array of maps ([a95a486](https://github.com/hyperspell/hyperspell-cli/commit/a95a486ccc528af84af0a8aa0500680982ee6033))
* fall back to main branch if linking fails in CI ([28b2498](https://github.com/hyperspell/hyperspell-cli/commit/28b24981adf8c8c39a68a44495912a66e63945ea))
* fix for failing to drop invalid module replace in link script ([ad3cbf1](https://github.com/hyperspell/hyperspell-cli/commit/ad3cbf15ab7a70f3318ae32d95b7f6bebeaebceb))
* fix quoting typo ([d4f3537](https://github.com/hyperspell/hyperspell-cli/commit/d4f35370e45ed2cc4deceeb6b8fae13f0a07fb63))
* flags for nullable body scalar fields are strictly typed ([c983439](https://github.com/hyperspell/hyperspell-cli/commit/c9834390753ff2facace1035b6b692e73a73e81a))


### Chores

* add documentation for ./scripts/link ([2586047](https://github.com/hyperspell/hyperspell-cli/commit/2586047b1af969fcd3b5c21e52c072a7b620118d))
* **ci:** support manually triggering release workflow ([b5836aa](https://github.com/hyperspell/hyperspell-cli/commit/b5836aa567c2ba79cf6ad7a8706de0ef196ed317))
* **cli:** additional test cases for `ShowJSONIterator` ([5784e71](https://github.com/hyperspell/hyperspell-cli/commit/5784e710f1021b0939a8985eedaa0c80b0b9df0b))
* **cli:** fall back to JSON when using default "explore" with non-TTY ([1b5c7e9](https://github.com/hyperspell/hyperspell-cli/commit/1b5c7e9de0a6ceaf663c5a06365a0ce1ec11cca4))
* **cli:** let `--format raw` be used in conjunction with `--transform` ([d9c0c77](https://github.com/hyperspell/hyperspell-cli/commit/d9c0c77f238264b97af2c5fa8aacbd71de7f6f4b))
* **cli:** switch long lists of positional args over to param structs ([4a9e250](https://github.com/hyperspell/hyperspell-cli/commit/4a9e2508c2605e814c40980baf343dbda59f28b8))
* **cli:** use `ShowJSONOpts` as argument to `formatJSON` instead of many positionals ([9e0c109](https://github.com/hyperspell/hyperspell-cli/commit/9e0c10938023ffbd46ca50c61baf2c3353df23d5))
* **internal:** more robust bootstrap script ([014bf27](https://github.com/hyperspell/hyperspell-cli/commit/014bf271f51526dab8ae459bd514bab798c8062d))
* mark all CLI-related tests in Go with `t.Parallel()` ([5fc2402](https://github.com/hyperspell/hyperspell-cli/commit/5fc240280f247a2a3817b37e359c7c674048a448))
* modify CLI tests to inject stdout so mutating `os.Stdout` isn't necessary ([b7fd553](https://github.com/hyperspell/hyperspell-cli/commit/b7fd5533571992857a741f834af4d272b8ad2b6d))
* switch some CLI Go tests from `os.Chdir` to `t.Chdir` ([2f49e45](https://github.com/hyperspell/hyperspell-cli/commit/2f49e45e69e821c97605e3929f4d6864f2b306e7))
* **tests:** bump steady to v0.22.1 ([522092e](https://github.com/hyperspell/hyperspell-cli/commit/522092e2767802d6e258353c4bb374f16f2751d5))


### Documentation

* update examples ([62e382b](https://github.com/hyperspell/hyperspell-cli/commit/62e382b449383807ab1568d97c5f461fed2c702c))

## 0.1.0 (2026-04-02)

Full Changelog: [v0.0.1...v0.1.0](https://github.com/hyperspell/hyperspell-cli/compare/v0.0.1...v0.1.0)
Expand Down
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ hyperspell [resource] <command> [flags...]
```sh
hyperspell memories add \
--api-key 'My API Key' \
--text text
--text ...
```

For details about specific commands, use the `--help` flag.
Expand Down Expand Up @@ -119,3 +119,23 @@ base64-encoding). Note that absolute paths will begin with `@file://` or
```bash
hyperspell <command> --arg @data://file.txt
```

## Linking different Go SDK versions

You can link the CLI against a different version of the Hyperspell Go SDK
for development purposes using the `./scripts/link` script.

To link to a specific version from a repository (version can be a branch,
git tag, or commit hash):

```bash
./scripts/link github.com/org/repo@version
```

To link to a local copy of the SDK:

```bash
./scripts/link ../path/to/hyperspell-go
```

If you run the link script without any arguments, it will default to `../hyperspell-go`.
14 changes: 13 additions & 1 deletion cmd/hyperspell/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ func main() {
prepareForAutocomplete(app)
}

if baseURL, ok := os.LookupEnv("HYPERSPELL_BASE_URL"); ok {
if err := cmd.ValidateBaseURL(baseURL, "HYPERSPELL_BASE_URL"); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
}

if err := app.Run(context.Background(), os.Args); err != nil {
exitCode := 1

Expand All @@ -36,7 +43,12 @@ func main() {
fmt.Fprintf(os.Stderr, "%s %q: %d %s\n", apierr.Request.Method, apierr.Request.URL, apierr.Response.StatusCode, http.StatusText(apierr.Response.StatusCode))
format := app.String("format-error")
json := gjson.Parse(apierr.RawJSON())
show_err := cmd.ShowJSON(os.Stdout, "Error", json, format, app.String("transform-error"))
show_err := cmd.ShowJSON(json, cmd.ShowJSONOpts{
ExplicitFormat: app.IsSet("format-error"),
Format: format,
Title: "Error",
Transform: app.String("transform-error"),
})
if show_err != nil {
// Just print the original error:
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
Expand Down
4 changes: 4 additions & 0 deletions internal/apiform/form_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,12 @@ var tests = map[string]struct {
}

func TestEncode(t *testing.T) {
t.Parallel()

for name, test := range tests {
t.Run(name, func(t *testing.T) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the range loop without capturing name and test into local variables causes a race condition on Go < 1.22 — all parallel subtests will close over the same loop variables and may execute with incorrect/final-iteration values.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file `internal/apiform/form_test.go`, at the `for name, test := range tests` loop (around line 90), the subtest closure captures the loop variables `name` and `test` by reference. After adding `t.Parallel()`, subtests run after the loop completes, meaning all subtests may share the same (last) values of `name` and `test` on Go versions < 1.22. Fix this by adding `name, test := name, test` immediately inside the loop body before the `t.Run` call to shadow the loop variables with per-iteration copies.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the range loop without capturing name and test into local variables causes a race condition on Go < 1.22 — all parallel subtests will close over the same loop variables and may execute with incorrect/final-iteration values.

Affected Locations:

  • internal/apiform/form_test.go:91-91
  • internal/apiquery/query_test.go:118-118
🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file `internal/apiform/form_test.go`, at the `for name, test := range tests` loop (around line 90), the subtest closure captures the loop variables `name` and `test` by reference. After adding `t.Parallel()`, subtests run after the loop completes, meaning all subtests may share the same (last) values of `name` and `test` on Go versions < 1.22. Fix this by adding `name, test := name, test` immediately inside the loop body before the `t.Run` call to shadow the loop variables with per-iteration copies.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: In Go < 1.22, name and test are shared loop variables — adding t.Parallel() inside the subtest causes all parallel subtests to reference the last iteration's values, making the tests silently incorrect.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file `internal/apiform/form_test.go`, around lines 88-91, the loop variables `name` and `test` are captured by reference inside a parallel subtest closure. In Go < 1.22, all parallel subtests will use the last iteration's values for `name` and `test`. Fix by adding `name, test := name, test` immediately after the `for` loop opening (before `t.Run`) to shadow the loop variables with per-iteration copies. This ensures each parallel subtest closure captures its own independent copy of the loop variables.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: With t.Parallel() added, the closure captures test by reference — in Go < 1.22, all subtests will race on the same loop variable and may execute with the wrong test value, causing flaky or incorrect test results.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file `internal/apiform/form_test.go`, at the `for name, test := range tests` loop (around line 89), the subtest closure now calls `t.Parallel()` which means the closure body executes after the loop has advanced. In Go versions below 1.22, `test` and `name` are shared loop variables that will have changed by the time the subtest runs. Fix this by adding `name, test := name, test` immediately before the `t.Run` call to shadow the loop variables with per-iteration copies.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the subtest causes name and test loop variables to be captured by reference — in Go < 1.22, all parallel subtests will run after the loop finishes and will share the last iteration's values, producing wrong or flaky test results.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file internal/apiform/form_test.go, inside the TestEncode function's for-range loop (around line 91), t.Parallel() was added to the subtest closure. This causes the classic Go loop-variable capture race: the closure captures `name` and `test` by reference, so in Go < 1.22 all parallel subtests will see the last iteration's values. Fix by adding `name, test := name, test` immediately after the `t.Run(name, func(t *testing.T) {` line opens, or immediately before the `t.Run` call, to shadow the loop variables with per-iteration copies.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the subtest closure causes the subtest to pause and resume after the loop ends, so test and name captured by the closure will refer to the last iteration's values in Go versions prior to 1.22 — all parallel subtests will run with the same (last) test case.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file internal/apiform/form_test.go, lines 89-91, the for-range loop captures `name` and `test` by reference in the t.Run closure. After adding t.Parallel() on line 91, all subtests are paused and the loop completes before they resume, meaning every subtest closure sees the same last value of `name` and `test` (on Go < 1.22). Fix by adding local variable shadowing immediately inside the for loop body, before the t.Run call:

```go
for name, test := range tests {
    name, test := name, test  // shadow loop vars for parallel closure capture
    t.Run(name, func(t *testing.T) {
        t.Parallel()
        ...
    })
}

</details>
<!-- ai_prompt_end -->

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the subtest causes the closure to capture the loop variable test by reference; if the project uses Go < 1.22, all parallel subtests will execute with the same (last) value of test, making most test assertions wrong or non-deterministic.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file `internal/apiform/form_test.go`, the `for name, test := range tests` loop at line ~88 now has `t.Parallel()` inside the subtest closure (added in this diff). In Go < 1.22, both `name` and `test` are captured by reference, so all parallel subtests will see the final loop iteration's values. Fix by adding `name, test := name, test` immediately after the `for` line to shadow the loop variables with per-iteration copies, ensuring each subtest closure captures its own value.

t.Parallel()

buf := bytes.NewBuffer(nil)
writer := multipart.NewWriter(buf)
writer.SetBoundary("xxx")
Expand Down
4 changes: 4 additions & 0 deletions internal/apiquery/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
)

func TestEncode(t *testing.T) {
t.Parallel()

tests := map[string]struct {
val any
settings QuerySettings
Expand Down Expand Up @@ -114,6 +116,8 @@ func TestEncode(t *testing.T) {

for name, test := range tests {
t.Run(name, func(t *testing.T) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the range loop without capturing name and test per iteration causes all subtests to close over the same loop variables — in Go < 1.22 they will all run with the last iteration's values, producing incorrect/flaky test results.

Affected Locations:

  • internal/apiquery/query_test.go:118-118
  • internal/requestflag/innerflag_test.go:31-31
  • internal/requestflag/requestflag_test.go:60-61
🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In internal/apiquery/query_test.go, around line 116-117, the for-range loop over `tests` was given a `t.Parallel()` call inside the `t.Run` callback. Without capturing the loop variables locally, all parallel subtests will share the same `name` and `test` variables (the last iteration's values in Go < 1.22), causing incorrect test behaviour. Add `name, test := name, test` immediately after the `t.Run` open brace (before `t.Parallel()`) to shadow and capture each iteration's values.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the subtest causes the closure to capture the test loop variable by reference; in Go < 1.22 all parallel subtests will race on the same test value, producing incorrect or flaky results. A local copy (test := test) is needed before t.Parallel() to pin the value per iteration.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In internal/apiquery/query_test.go, around line 118, the subtest closure added `t.Parallel()` but the `test` loop variable is captured by reference. In Go versions before 1.22, all parallel subtests will share the same loop variable, causing data races and incorrect test behavior. Fix by adding `test := test` immediately before `t.Parallel()` inside the closure to create a per-iteration copy of the loop variable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the for name, test := range tests loop without first capturing name and test as local variables causes all parallel subtests to close over the same loop variables — in Go < 1.22 this means every subtest may execute with the final iteration's values, producing incorrect or non-deterministic test results.

Affected Locations:

  • internal/apiquery/query_test.go:118-118
  • internal/requestflag/innerflag_test.go:31-31
  • pkg/cmd/flagoptions_test.go:223-224
🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In internal/apiquery/query_test.go, inside the `for name, test := range tests` loop (around line 116), `t.Parallel()` was added on line 118 without first capturing the loop variables. Add `name, test := name, test` immediately before `t.Parallel()` inside the `t.Run` closure to avoid the classic Go loop-variable capture race condition in Go versions < 1.22, where all parallel subtests could end up using the final iteration's `test` value.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside a for name, test := range tests loop without re-declaring the loop variables causes all parallel subtests to share the same name and test values (the last iteration's) in Go versions prior to 1.22, making every subtest run with wrong inputs.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file internal/apiquery/query_test.go, around line 118, t.Parallel() was added inside a for-range loop subtest without capturing the loop variables. In Go < 1.22, this causes all parallel subtests to close over the same loop variables (last iteration's values). Fix by adding `name := name` and `test := test` immediately before `t.Parallel()` inside the t.Run closure to ensure each goroutine captures its own copy of the loop variables.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the subtest closure without shadowing name and test causes all parallel subtests to capture the same loop variables by reference (in Go < 1.22), making every subtest run with the last iteration's values and producing incorrect or flaky test results.

Affected Locations:

  • internal/apiquery/query_test.go:118-118
  • pkg/cmd/flagoptions_test.go:222-224
🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file internal/apiquery/query_test.go, around line 116-118, the diff adds t.Parallel() inside a subtest loop that closes over the loop variables `name` and `test`. In Go versions prior to 1.22, this causes all parallel subtests to share the same variable references, meaning they all run with the last iteration's values. Fix this by adding `name, test := name, test` immediately after the `t.Run(name, func(t *testing.T) {` line (or before the t.Run call) to create per-iteration local copies of both variables before calling t.Parallel().

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the subtest without capturing test and name into local variables causes all parallel subtests to share the loop iteration variables, so in Go < 1.22 every subtest races on the same final value of test, producing incorrect or flaky results.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file `internal/apiquery/query_test.go`, at the `for name, test := range tests` loop (around line 115), the diff adds `t.Parallel()` inside the subtest closure without first shadowing the loop variables. In Go versions before 1.22 this causes all parallel subtests to close over the same `name` and `test` variables, racing to use whatever final values the loop left. Fix by adding `name, test := name, test` immediately after the opening brace of the `for` body (before `t.Run`), or equivalently inside the subtest closure before `t.Parallel()` is called.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() here causes the subtests to capture the loop variables name and test by reference — on Go < 1.22, all parallel subtests will run with the last iteration's values, making the tests non-deterministic or silently testing the wrong inputs.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file internal/apiquery/query_test.go, at the for-loop starting around line 116, `t.Parallel()` was added inside `t.Run` without re-declaring the loop variables. On Go < 1.22, all parallel subtests will share the same `name` and `test` variables (captured by reference from the outer loop), causing them all to run with the final iteration's values. Fix by adding `name, test := name, test` immediately after the `t.Run(name, func(t *testing.T) {` line and before `t.Parallel()`, so each closure captures its own copy of the loop variables.

t.Parallel()
Comment on lines 118 to +119
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the range loop without capturing test as a local variable causes all parallel subtests to share the same loop variable — in Go < 1.22 they will all run with the last iteration's test value, producing silent wrong results or false passes.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file internal/apiquery/query_test.go, at the t.Run subtest closure starting around line 117, t.Parallel() was added without first capturing the loop variable `test` into a local variable. In Go versions < 1.22, this causes all parallel subtests to share the final iteration's value of `test`. Fix: add `test := test` immediately after `t.Parallel()` (line 119) to shadow the loop variable with a per-iteration copy.

Comment on lines 118 to +119
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the range loop without capturing test and name into local variables causes all parallel subtests to share the loop variables — in Go < 1.22 every subtest will likely read the last iteration's value of test, making tests silently pass with wrong inputs or produce misleading failures.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file internal/apiquery/query_test.go, at the for-range loop starting around line 116, the subtest closure captures the loop variables `name` and `test` by reference. After adding `t.Parallel()` on line 118, these subtests run concurrently while the outer loop continues advancing `name` and `test`. On Go versions < 1.22 this causes all subtests to see the last loop iteration's values. Fix by shadowing the loop variables at the top of the loop body, before the `t.Run` call: add `name, test := name, test` immediately after `for name, test := range tests {`.

Comment on lines 118 to +119
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: Adding t.Parallel() inside the for name, test := range tests loop causes all parallel subtests to capture test by reference in Go < 1.22 — by the time each subtest body runs, test will hold the last map iteration value, making every subtest use the same inputs and producing false passes.

Affected Locations:

  • internal/apiquery/query_test.go:118-119
  • internal/apiform/form_test.go:91-91
🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In file `internal/apiquery/query_test.go`, inside the `for name, test := range tests` loop starting around line 116, the subtest closure captures the loop variable `test` by reference. Now that `t.Parallel()` has been added at line 118-119, all subtests run concurrently after the loop finishes, so every subtest will see the same (last) value of `test` in Go versions before 1.22. Fix this by adding `name, test := name, test` immediately after the `t.Run(name, func(t *testing.T) {` line and before `t.Parallel()` to create per-iteration copies of the loop variables.


query := map[string]any{"query": test.val}
values, err := MarshalWithSettings(query, test.settings)
if err != nil {
Expand Down
40 changes: 40 additions & 0 deletions internal/autocomplete/autocomplete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
)

func TestGetCompletions_EmptyArgs(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "generate", Usage: "Generate SDK"},
Expand All @@ -26,6 +28,8 @@ func TestGetCompletions_EmptyArgs(t *testing.T) {
}

func TestGetCompletions_SubcommandPrefix(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "generate", Usage: "Generate SDK"},
Expand All @@ -43,6 +47,8 @@ func TestGetCompletions_SubcommandPrefix(t *testing.T) {
}

func TestGetCompletions_HiddenCommand(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "visible", Usage: "Visible command"},
Expand All @@ -57,6 +63,8 @@ func TestGetCompletions_HiddenCommand(t *testing.T) {
}

func TestGetCompletions_NestedSubcommand(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -79,6 +87,8 @@ func TestGetCompletions_NestedSubcommand(t *testing.T) {
}

func TestGetCompletions_FlagCompletion(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -102,6 +112,8 @@ func TestGetCompletions_FlagCompletion(t *testing.T) {
}

func TestGetCompletions_ShortFlagCompletion(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -123,6 +135,8 @@ func TestGetCompletions_ShortFlagCompletion(t *testing.T) {
}

func TestGetCompletions_FileFlagBehavior(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -142,6 +156,8 @@ func TestGetCompletions_FileFlagBehavior(t *testing.T) {
}

func TestGetCompletions_NonBoolFlagValue(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -161,6 +177,8 @@ func TestGetCompletions_NonBoolFlagValue(t *testing.T) {
}

func TestGetCompletions_BoolFlagDoesNotBlockCompletion(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -185,6 +203,8 @@ func TestGetCompletions_BoolFlagDoesNotBlockCompletion(t *testing.T) {
}

func TestGetCompletions_ColonCommands_NoColonTyped(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "config:get", Usage: "Get config value"},
Expand All @@ -202,6 +222,8 @@ func TestGetCompletions_ColonCommands_NoColonTyped(t *testing.T) {
}

func TestGetCompletions_ColonCommands_ColonTyped_Bash(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "config:get", Usage: "Get config value"},
Expand All @@ -221,6 +243,8 @@ func TestGetCompletions_ColonCommands_ColonTyped_Bash(t *testing.T) {
}

func TestGetCompletions_ColonCommands_ColonTyped_Zsh(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "config:get", Usage: "Get config value"},
Expand All @@ -240,6 +264,8 @@ func TestGetCompletions_ColonCommands_ColonTyped_Zsh(t *testing.T) {
}

func TestGetCompletions_BashStyleColonCompletion(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "config:get", Usage: "Get config value"},
Expand All @@ -257,6 +283,8 @@ func TestGetCompletions_BashStyleColonCompletion(t *testing.T) {
}

func TestGetCompletions_BashStyleColonCompletion_NoMatch(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "config:get", Usage: "Get config value"},
Expand All @@ -271,6 +299,8 @@ func TestGetCompletions_BashStyleColonCompletion_NoMatch(t *testing.T) {
}

func TestGetCompletions_ZshStyleColonCompletion(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "config:get", Usage: "Get config value"},
Expand All @@ -287,6 +317,8 @@ func TestGetCompletions_ZshStyleColonCompletion(t *testing.T) {
}

func TestGetCompletions_MixedColonAndRegularCommands(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "generate", Usage: "Generate SDK"},
Expand All @@ -305,6 +337,8 @@ func TestGetCompletions_MixedColonAndRegularCommands(t *testing.T) {
}

func TestGetCompletions_FlagWithBoolFlagSkipsValue(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -329,6 +363,8 @@ func TestGetCompletions_FlagWithBoolFlagSkipsValue(t *testing.T) {
}

func TestGetCompletions_MultipleFlagsBeforeSubcommand(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -353,6 +389,8 @@ func TestGetCompletions_MultipleFlagsBeforeSubcommand(t *testing.T) {
}

func TestGetCompletions_CommandAliases(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "generate", Aliases: []string{"gen", "g"}, Usage: "Generate SDK"},
Expand All @@ -372,6 +410,8 @@ func TestGetCompletions_CommandAliases(t *testing.T) {
}

func TestGetCompletions_AllFlagsWhenNoPrefix(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand Down
14 changes: 12 additions & 2 deletions internal/autocomplete/shellscripts/zsh_autocomplete.zsh
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/bin/zsh
compdef ____APPNAME___zsh_autocomplete __APPNAME__
#compdef __APPNAME__

____APPNAME___zsh_autocomplete() {

Expand Down Expand Up @@ -44,3 +43,14 @@ ____APPNAME___zsh_autocomplete() {
;;
esac
}

# When installed in fpath (e.g., via Homebrew's zsh_completion stanza), this file
# is autoloaded as the function ___APPNAME__ and its body becomes that function's
# body. Detect that case via funcstack and dispatch to the completion function.
# When sourced (e.g., `source <(__APPNAME__ @completion zsh)`), register the
# function with compdef instead.
if [[ "${funcstack[1]}" = "___APPNAME__" ]]; then
____APPNAME___zsh_autocomplete "$@"
else
compdef ____APPNAME___zsh_autocomplete __APPNAME__
fi
6 changes: 6 additions & 0 deletions internal/jsonview/explorer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
)

func TestNavigateForward_EmptyRowData(t *testing.T) {
t.Parallel()

// An empty JSON array produces a TableView with no rows.
emptyArray := gjson.Parse("[]")
view, err := newTableView("", emptyArray, false)
Expand Down Expand Up @@ -38,6 +40,8 @@ type rawJSONItem struct {
func (r rawJSONItem) RawJSON() string { return r.raw }

func TestMarshalItemsToJSONArray_WithHasRawJSON(t *testing.T) {
t.Parallel()

items := []any{
rawJSONItem{raw: `{"id":1,"name":"alice"}`},
rawJSONItem{raw: `{"id":2,"name":"bob"}`},
Expand All @@ -49,6 +53,8 @@ func TestMarshalItemsToJSONArray_WithHasRawJSON(t *testing.T) {
}

func TestMarshalItemsToJSONArray_WithoutHasRawJSON(t *testing.T) {
t.Parallel()

items := []any{
map[string]any{"id": 1, "name": "alice"},
map[string]any{"id": 2, "name": "bob"},
Expand Down
Loading
Loading