← back to Cli Printing Press
fix(cli): exempt generated-client source helpers (#563)
c0639cf09d1d58cf555c54ea7f451f5f14e73007 · 2026-05-03 21:38:27 -0700 · Trevin Chow
Files touched
M internal/pipeline/internal_packages.goM internal/pipeline/source_client_check.goM internal/pipeline/source_client_check_test.go
Diff
commit c0639cf09d1d58cf555c54ea7f451f5f14e73007
Author: Trevin Chow <trevin@trevinchow.com>
Date: Sun May 3 21:38:27 2026 -0700
fix(cli): exempt generated-client source helpers (#563)
---
internal/pipeline/internal_packages.go | 21 ++++++---
internal/pipeline/source_client_check.go | 9 ++++
internal/pipeline/source_client_check_test.go | 62 +++++++++++++++++++++++++++
3 files changed, 87 insertions(+), 5 deletions(-)
diff --git a/internal/pipeline/internal_packages.go b/internal/pipeline/internal_packages.go
index b39e25f2..e313a63f 100644
--- a/internal/pipeline/internal_packages.go
+++ b/internal/pipeline/internal_packages.go
@@ -20,11 +20,22 @@ var reservedInternalPackages = map[string]bool{
"graphql": true,
}
+const (
+ rawOutboundHTTPCallPattern = `\bhttp\.(?:Get|Post|NewRequest(?:WithContext)?|Do)\s*\(|` +
+ `\b\w+\.HTTPClient\.Do\s*\(|` +
+ `\b\w+\.HTTP\.Do\s*\(`
+ generatedClientReceiverCallPattern = `\bc\.(?:Do|Get|Post)\s*\(`
+)
+
+// rawOutboundHTTPCallRe matches outbound HTTP request shapes that bypass the
+// generated client. Files with these calls need local limiter and typed 429
+// handling when they live in hand-written sibling internal packages.
+var rawOutboundHTTPCallRe = regexp.MustCompile(rawOutboundHTTPCallPattern)
+
+var generatedClientParamRe = regexp.MustCompile(`\bc\s+\*client\.Client\b`)
+var generatedClientReceiverCallRe = regexp.MustCompile(generatedClientReceiverCallPattern)
+
// outboundHTTPCallRe matches every outbound HTTP request shape that appears in
// generated and agent-authored Go code. Centralized so reimplementation_check
// (per-command) and source_client_check (per-sibling-package) cannot diverge.
-var outboundHTTPCallRe = regexp.MustCompile(
- `\bhttp\.(?:Get|Post|NewRequest(?:WithContext)?|Do)\s*\(|` +
- `\b\w+\.HTTPClient\.Do\s*\(|` +
- `\b\w+\.HTTP\.Do\s*\(|` +
- `\bc\.(?:Do|Get|Post)\s*\(`)
+var outboundHTTPCallRe = regexp.MustCompile(rawOutboundHTTPCallPattern + `|` + generatedClientReceiverCallPattern)
diff --git a/internal/pipeline/source_client_check.go b/internal/pipeline/source_client_check.go
index bb464eca..fe3f8c43 100644
--- a/internal/pipeline/source_client_check.go
+++ b/internal/pipeline/source_client_check.go
@@ -104,6 +104,9 @@ func walkSourcePackage(cliDir, pkgDir string, result *SourceClientCheckResult) {
if !outboundHTTPCallRe.MatchString(content) {
return nil
}
+ if hasGeneratedClientMediatedCall(content) && !rawOutboundHTTPCallRe.MatchString(content) {
+ return nil
+ }
hasLimiter := limiterUseRe.MatchString(content)
hasTypedError := rateLimitErrorRe.MatchString(content)
@@ -129,3 +132,9 @@ func walkSourcePackage(cliDir, pkgDir string, result *SourceClientCheckResult) {
return nil
})
}
+
+func hasGeneratedClientMediatedCall(content string) bool {
+ return clientImportRe.MatchString(content) &&
+ generatedClientParamRe.MatchString(content) &&
+ generatedClientReceiverCallRe.MatchString(content)
+}
diff --git a/internal/pipeline/source_client_check_test.go b/internal/pipeline/source_client_check_test.go
index a0437590..4651db8b 100644
--- a/internal/pipeline/source_client_check_test.go
+++ b/internal/pipeline/source_client_check_test.go
@@ -81,6 +81,51 @@ func (c *Client) Fetch(url string) error {
if resp.StatusCode == 429 { return nil }
return nil
}
+`
+ const generatedClientHelper = `package sec
+
+import (
+ "context"
+
+ "example.com/foo/internal/client"
+)
+
+func Fetch(ctx context.Context, c *client.Client, path string) ([]byte, error) {
+ return c.Get(ctx, path, nil, nil)
+}
+`
+ const rawHTTPWithGeneratedClientImport = `package sec
+
+import (
+ "net/http"
+
+ "example.com/foo/internal/client"
+)
+
+var _ *client.Client
+
+func Fetch(url string) error {
+ resp, err := http.Get(url)
+ if err != nil { return err }
+ _ = resp
+ return nil
+}
+`
+ const generatedClientImportWithUnrelatedCGet = `package sec
+
+import "example.com/foo/internal/client"
+
+var _ *client.Client
+
+type Cache struct{}
+
+func (c *Cache) Get(key string) ([]byte, error) {
+ return nil, nil
+}
+
+func Fetch(c *Cache, key string) ([]byte, error) {
+ return c.Get(key)
+}
`
const limiterButNo429 = `package sec
@@ -232,6 +277,23 @@ func (c *Client) Fetch(url string) error {
wantReasonHas: "rate limiter",
wantPackage: "source/sec",
},
+ {
+ name: "generated client helper passes without local limiter",
+ files: map[string]string{"source/sec/sec.go": generatedClientHelper},
+ wantCheckedPos: true,
+ },
+ {
+ name: "generated client import does not exempt raw HTTP",
+ files: map[string]string{"source/sec/sec.go": rawHTTPWithGeneratedClientImport},
+ wantFindings: 1,
+ wantReasonHas: "without rate limiter or typed 429",
+ },
+ {
+ name: "generated client import does not exempt unrelated c Get",
+ files: map[string]string{"source/sec/sec.go": generatedClientImportWithUnrelatedCGet},
+ wantFindings: 1,
+ wantReasonHas: "without rate limiter or typed 429",
+ },
{
name: "flags swallowed 429",
files: map[string]string{"source/sec/sec.go": limiterButNo429},
← 6242e101 fix(cli): add client-call reimplementation directive (#562)
·
back to Cli Printing Press
·
fix(cli): reclaim locks from dead owners (#564) 34413e94 →