[object Object]

← 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

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 →