[pkgsite] cmd/internal/pkgsite-cli: implement auto-pagination and streaming

1 view
Skip to first unread message

Hyang-Ah Hana Kim (Gerrit)

unread,
Apr 27, 2026, 6:32:32 PM (2 days ago) Apr 27
to goph...@pubsubhelper.golang.org, Hyang-Ah Hana Kim, golang-co...@googlegroups.com

Hyang-Ah Hana Kim has uploaded the change for review

Commit message

cmd/internal/pkgsite-cli: implement auto-pagination and streaming

- Support automatic pagination up to requested limit.
- Add --stream flag for live output (JSONL for JSON mode).
- Remove --token flag as it was too low-level.

For golang/go#78690
Change-Id: Ic9c01b80fe6df6f673c6512b188907d8bd1580fa

Change diff

diff --git a/cmd/internal/pkgsite-cli/client/client.go b/cmd/internal/pkgsite-cli/client/client.go
index f5a54e2..2453dd0 100644
--- a/cmd/internal/pkgsite-cli/client/client.go
+++ b/cmd/internal/pkgsite-cli/client/client.go
@@ -11,6 +11,7 @@
"encoding/json"
"fmt"
"io"
+ "iter"
"net/http"
"net/url"
"strconv"
@@ -32,6 +33,57 @@
}
}

+// Paginate returns an iterator that yields items from a paginated API.
+// It handles fetching subsequent pages using the token returned by the fetch function.
+func Paginate[T any](startToken string, fetch func(token string) (*PaginatedResponse[T], error)) iter.Seq2[T, error] {
+ return func(yield func(T, error) bool) {
+ token := startToken
+ for {
+ resp, err := fetch(token)
+ if err != nil {
+ var zero T
+ yield(zero, err)
+ return
+ }
+ for _, item := range resp.Items {
+ if !yield(item, nil) {
+ return
+ }
+ }
+ token = resp.NextPageToken
+ if token == "" {
+ return
+ }
+ }
+ }
+}
+
+// PaginateAll fetches all pages (or up to limit) and returns the aggregated items and total.
+func PaginateAll[T any](startToken string, fetch func(token string) (*PaginatedResponse[T], error), limit int) ([]T, int, error) {
+ var allItems []T
+ var total int
+
+ resp, err := fetch(startToken)
+ if err != nil {
+ return nil, 0, err
+ }
+ allItems = append(allItems, resp.Items...)
+ total = resp.Total
+
+ if resp.NextPageToken != "" && (limit == 0 || len(allItems) < limit) {
+ for item, err := range Paginate(resp.NextPageToken, fetch) {
+ if err != nil {
+ return nil, 0, err
+ }
+ allItems = append(allItems, item)
+ if limit > 0 && len(allItems) >= limit {
+ break
+ }
+ }
+ }
+ return allItems, total, nil
+}
+
func (e *Error) Error() string {
if len(e.Candidates) > 0 {
var b strings.Builder
diff --git a/cmd/internal/pkgsite-cli/command.go b/cmd/internal/pkgsite-cli/command.go
index 1b8d47e..8a487f5 100644
--- a/cmd/internal/pkgsite-cli/command.go
+++ b/cmd/internal/pkgsite-cli/command.go
@@ -152,14 +152,14 @@
type commonFlags struct {
jsonOut bool
limit int
- token string
+ stream bool
server string
}

func (f *commonFlags) register(fs *flag.FlagSet) {
fs.BoolVar(&f.jsonOut, "json", false, "output JSON")
fs.IntVar(&f.limit, "limit", 0, "max results (default: 20 text, 100 json)")
- fs.StringVar(&f.token, "token", "", "pagination token (JSON mode)")
+ fs.BoolVar(&f.stream, "stream", false, "stream results as they arrive")
fs.StringVar(&f.server, "server", defaultServer, "API server URL")
}

diff --git a/cmd/internal/pkgsite-cli/format.go b/cmd/internal/pkgsite-cli/format.go
index 491df31..fea29d5 100644
--- a/cmd/internal/pkgsite-cli/format.go
+++ b/cmd/internal/pkgsite-cli/format.go
@@ -160,16 +160,20 @@
return
}
for _, sr := range r.Items {
- fmt.Fprintf(w, "%s\n", sr.PackagePath)
- fmt.Fprintf(w, " Module: %s@%s\n", sr.ModulePath, sr.Version)
- if sr.Synopsis != "" {
- fmt.Fprintf(w, " Synopsis: %s\n", sr.Synopsis)
- }
- fmt.Fprintln(w)
+ formatSearchResult(w, sr)
}
formatPaginationHint(w, len(r.Items), r.Total)
}

+func formatSearchResult(w io.Writer, sr client.SearchResult) {
+ fmt.Fprintf(w, "%s\n", sr.PackagePath)
+ fmt.Fprintf(w, " Module: %s@%s\n", sr.ModulePath, sr.Version)
+ if sr.Synopsis != "" {
+ fmt.Fprintf(w, " Synopsis: %s\n", sr.Synopsis)
+ }
+ fmt.Fprintln(w)
+}
+
func formatLicenses(w io.Writer, licenses []client.License) {
fmt.Fprintln(w, "Licenses:")
for _, l := range licenses {
@@ -178,7 +182,6 @@
}

func formatPaginationHint(w io.Writer, shown, total int) {
- // TODO(hyangah): show how to use token.
if total > shown {
fmt.Fprintf(w, " Showing %d of %d. Use --limit=N to see more.\n", shown, total)
}
diff --git a/cmd/internal/pkgsite-cli/main_test.go b/cmd/internal/pkgsite-cli/main_test.go
index 097eaa8..0af23c4 100644
--- a/cmd/internal/pkgsite-cli/main_test.go
+++ b/cmd/internal/pkgsite-cli/main_test.go
@@ -162,6 +162,137 @@
}
}

+func TestRunSearchPagination(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ token := r.URL.Query().Get("token")
+ if token == "" {
+ json.NewEncoder(w).Encode(client.PaginatedResponse[client.SearchResult]{
+ Items: []client.SearchResult{{
+ PackagePath: "encoding/json",
+ ModulePath: "std",
+ Version: "go1.22.0",
+ }},
+ Total: 2,
+ NextPageToken: "next-token-123",
+ })
+ } else if token == "next-token-123" {
+ json.NewEncoder(w).Encode(client.PaginatedResponse[client.SearchResult]{
+ Items: []client.SearchResult{{
+ PackagePath: "encoding/xml",
+ ModulePath: "std",
+ Version: "go1.22.0",
+ }},
+ Total: 2,
+ NextPageToken: "",
+ })
+ } else {
+ http.Error(w, "invalid token", http.StatusBadRequest)
+ }
+ }))
+ defer srv.Close()
+
+ var stdout, stderr bytes.Buffer
+ code := run([]string{"--server=" + srv.URL, "search", "json"}, &stdout, &stderr)
+ if code != 0 {
+ t.Fatalf("exit code = %d, stderr = %s", code, stderr.String())
+ }
+ out := stdout.String()
+ if !strings.Contains(out, "encoding/json") {
+ t.Errorf("output missing first page result:\n%s", out)
+ }
+ if !strings.Contains(out, "encoding/xml") {
+ t.Errorf("output missing second page result:\n%s", out)
+ }
+}
+
+func TestRunSearchPaginationWithLimit(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ token := r.URL.Query().Get("token")
+ if token == "" {
+ json.NewEncoder(w).Encode(client.PaginatedResponse[client.SearchResult]{
+ Items: []client.SearchResult{{
+ PackagePath: "encoding/json",
+ ModulePath: "std",
+ Version: "go1.22.0",
+ }},
+ Total: 2,
+ NextPageToken: "next-token-123",
+ })
+ } else if token == "next-token-123" {
+ json.NewEncoder(w).Encode(client.PaginatedResponse[client.SearchResult]{
+ Items: []client.SearchResult{{
+ PackagePath: "encoding/xml",
+ ModulePath: "std",
+ Version: "go1.22.0",
+ }},
+ Total: 2,
+ NextPageToken: "",
+ })
+ } else {
+ http.Error(w, "invalid token", http.StatusBadRequest)
+ }
+ }))
+ defer srv.Close()
+
+ var stdout, stderr bytes.Buffer
+ // Limit is 1, so it should only fetch the first page and stop.
+ code := run([]string{"--server=" + srv.URL, "search", "--limit=1", "json"}, &stdout, &stderr)
+ if code != 0 {
+ t.Fatalf("exit code = %d, stderr = %s", code, stderr.String())
+ }
+ out := stdout.String()
+ if !strings.Contains(out, "encoding/json") {
+ t.Errorf("output missing first page result:\n%s", out)
+ }
+ if strings.Contains(out, "encoding/xml") {
+ t.Errorf("output should not contain second page result when limit is 1:\n%s", out)
+ }
+}
+
+func TestRunSearchStream(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ json.NewEncoder(w).Encode(client.PaginatedResponse[client.SearchResult]{
+ Items: []client.SearchResult{{
+ PackagePath: "encoding/json",
+ }},
+ Total: 1,
+ })
+ }))
+ defer srv.Close()
+
+ var stdout, stderr bytes.Buffer
+ code := run([]string{"--server=" + srv.URL, "search", "--stream", "json"}, &stdout, &stderr)
+ if code != 0 {
+ t.Fatalf("exit code = %d, stderr = %s", code, stderr.String())
+ }
+ out := stdout.String()
+ if !strings.Contains(out, "encoding/json") {
+ t.Errorf("output missing result:\n%s", out)
+ }
+}
+
+func TestRunSearchStreamJSON(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ json.NewEncoder(w).Encode(client.PaginatedResponse[client.SearchResult]{
+ Items: []client.SearchResult{{
+ PackagePath: "encoding/json",
+ }},
+ Total: 1,
+ })
+ }))
+ defer srv.Close()
+
+ var stdout, stderr bytes.Buffer
+ code := run([]string{"--server=" + srv.URL, "search", "--json", "--stream", "json"}, &stdout, &stderr)
+ if code != 0 {
+ t.Fatalf("exit code = %d, stderr = %s", code, stderr.String())
+ }
+ out := stdout.String()
+ if !strings.Contains(out, `"packagePath":"encoding/json"`) {
+ t.Errorf("output missing JSON result:\n%s", out)
+ }
+}
+
func TestRunAPIError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
diff --git a/cmd/internal/pkgsite-cli/module.go b/cmd/internal/pkgsite-cli/module.go
index 3d131f0..4afcb2c 100644
--- a/cmd/internal/pkgsite-cli/module.go
+++ b/cmd/internal/pkgsite-cli/module.go
@@ -45,32 +45,59 @@

if m.versions {
g.Go(func() error {
- var err error
- vers, err = c.GetVersions(gctx, path, client.PaginationOptions{
- Limit: m.effectiveLimit(),
- Token: m.token,
- })
- return err
+ fetch := func(token string) (*client.PaginatedResponse[client.VersionResponse], error) {
+ return c.GetVersions(gctx, path, client.PaginationOptions{
+ Limit: m.effectiveLimit(),
+ Token: token,
+ })
+ }
+ items, total, err := client.PaginateAll("", fetch, m.effectiveLimit())
+ if err != nil {
+ return err
+ }
+ vers = &client.PaginatedResponse[client.VersionResponse]{
+ Items: items,
+ Total: total,
+ }
+ return nil
})
}
if m.vulns {
g.Go(func() error {
- var err error
- vulns, err = c.GetVulns(gctx, path, version, client.PaginationOptions{
- Limit: m.effectiveLimit(),
- Token: m.token,
- })
- return err
+ fetch := func(token string) (*client.PaginatedResponse[client.Vulnerability], error) {
+ return c.GetVulns(gctx, path, version, client.PaginationOptions{
+ Limit: m.effectiveLimit(),
+ Token: token,
+ })
+ }
+ items, total, err := client.PaginateAll("", fetch, m.effectiveLimit())
+ if err != nil {
+ return err
+ }
+ vulns = &client.PaginatedResponse[client.Vulnerability]{
+ Items: items,
+ Total: total,
+ }
+ return nil
})
}
if m.packages {
g.Go(func() error {
- var err error
- pkgs, err = c.GetPackages(gctx, path, version, client.PaginationOptions{
- Limit: m.effectiveLimit(),
- Token: m.token,
- })
- return err
+ fetch := func(token string) (*client.PaginatedResponse[client.ModulePackageResponse], error) {
+ return c.GetPackages(gctx, path, version, client.PaginationOptions{
+ Limit: m.effectiveLimit(),
+ Token: token,
+ })
+ }
+ items, total, err := client.PaginateAll("", fetch, m.effectiveLimit())
+ if err != nil {
+ return err
+ }
+ pkgs = &client.PaginatedResponse[client.ModulePackageResponse]{
+ Items: items,
+ Total: total,
+ }
+ return nil
})
}

diff --git a/cmd/internal/pkgsite-cli/package.go b/cmd/internal/pkgsite-cli/package.go
index 1293cc6..9e840ee 100644
--- a/cmd/internal/pkgsite-cli/package.go
+++ b/cmd/internal/pkgsite-cli/package.go
@@ -42,34 +42,62 @@
result := packageResult{Package: pkg}

if p.symbols {
- syms, err := c.GetSymbols(ctx, path, version, client.SymbolsOptions{
- Module: p.module,
- GOOS: p.goos,
- GOARCH: p.goarch,
- PaginationOptions: client.PaginationOptions{
- Limit: p.effectiveLimit(),
- Token: p.token,
- },
- })
+ fetch := func(token string) (*client.PaginatedResponse[client.Symbol], error) {
+ return c.GetSymbols(ctx, path, version, client.SymbolsOptions{
+ Module: p.module,
+ GOOS: p.goos,
+ GOARCH: p.goarch,
+ PaginationOptions: client.PaginationOptions{
+ Limit: p.effectiveLimit(),
+ Token: token,
+ },
+ })
+ }
+ items, total, err := client.PaginateAll("", fetch, p.effectiveLimit())
if err != nil {
handleErr(stdout, stderr, err, p.jsonOut)
return 1
}
- result.Symbols = syms
+ result.Symbols = &client.PaginatedResponse[client.Symbol]{
+ Items: items,
+ Total: total,
+ }
}
if p.importedBy {
- ib, err := c.GetImportedBy(ctx, path, version, client.ImportedByOptions{
+ resp, err := c.GetImportedBy(ctx, path, version, client.ImportedByOptions{
Module: p.module,
PaginationOptions: client.PaginationOptions{
Limit: p.effectiveLimit(),
- Token: p.token,
+ Token: "",
},
})
if err != nil {
handleErr(stdout, stderr, err, p.jsonOut)
return 1
}
- result.ImportedBy = ib
+ if resp.ImportedBy.NextPageToken != "" && len(resp.ImportedBy.Items) < p.effectiveLimit() {
+ fetch := func(token string) (*client.PaginatedResponse[string], error) {
+ r, err := c.GetImportedBy(ctx, path, version, client.ImportedByOptions{
+ Module: p.module,
+ PaginationOptions: client.PaginationOptions{
+ Limit: p.effectiveLimit(),
+ Token: token,
+ },
+ })
+ if err != nil {
+ return nil, err
+ }
+ return &r.ImportedBy, nil
+ }
+ items, total, err := client.PaginateAll(resp.ImportedBy.NextPageToken, fetch, p.effectiveLimit())
+ if err != nil {
+ handleErr(stdout, stderr, err, p.jsonOut)
+ return 1
+ }
+ resp.ImportedBy.Items = append(resp.ImportedBy.Items, items...)
+ resp.ImportedBy.Total = total
+ }
+ result.ImportedBy = resp
}

if p.jsonOut {
diff --git a/cmd/internal/pkgsite-cli/search.go b/cmd/internal/pkgsite-cli/search.go
index e63cc58..df31a02 100644
--- a/cmd/internal/pkgsite-cli/search.go
+++ b/cmd/internal/pkgsite-cli/search.go
@@ -6,6 +6,7 @@

import (
"context"
+ "encoding/json"
"flag"
"io"
"strings"
@@ -25,17 +26,78 @@
defer cancel()

c := client.NewClient(s.server)
- results, err := c.Search(ctx, query, client.SearchOptions{
- Symbol: s.symbol,
- PaginationOptions: client.PaginationOptions{
- Limit: s.effectiveLimit(),
- Token: s.token,
- },
- })
+
+ fetch := func(token string) (*client.PaginatedResponse[client.SearchResult], error) {
+ return c.Search(ctx, query, client.SearchOptions{
+ Symbol: s.symbol,
+ PaginationOptions: client.PaginationOptions{
+ Limit: s.effectiveLimit(),
+ Token: token,
+ },
+ })
+ }
+
+ var results *client.PaginatedResponse[client.SearchResult]
+ var err error
+
+ targetLimit := s.effectiveLimit()
+
+ if s.stream {
+ var count int
+ var total int
+
+ outItem := func(item client.SearchResult) {
+ if s.jsonOut {
+ json.NewEncoder(stdout).Encode(item)
+ } else {
+ formatSearchResult(stdout, item)
+ }
+ }
+
+ resp, err := fetch("")
+ if err != nil {
+ handleErr(stdout, stderr, err, s.jsonOut)
+ return 1
+ }
+
+ total = resp.Total
+ for _, item := range resp.Items {
+ outItem(item)
+ count++
+ if count >= targetLimit {
+ break
+ }
+ }
+
+ if resp.NextPageToken != "" && count < targetLimit {
+ for item, err := range client.Paginate(resp.NextPageToken, fetch) {
+ if err != nil {
+ handleErr(stdout, stderr, err, s.jsonOut)
+ return 1
+ }
+ outItem(item)
+ count++
+ if count >= targetLimit {
+ break
+ }
+ }
+ }
+
+ if !s.jsonOut {
+ formatPaginationHint(stdout, count, total)
+ }
+ return 0
+ }
+
+ items, total, err := client.PaginateAll("", fetch, targetLimit)
if err != nil {
handleErr(stdout, stderr, err, s.jsonOut)
return 1
}
+ results = &client.PaginatedResponse[client.SearchResult]{
+ Items: items,
+ Total: total,
+ }

if s.jsonOut {
return writeJSON(stdout, stderr, results)

Change information

Files:
  • M cmd/internal/pkgsite-cli/client/client.go
  • M cmd/internal/pkgsite-cli/command.go
  • M cmd/internal/pkgsite-cli/format.go
  • M cmd/internal/pkgsite-cli/main_test.go
  • M cmd/internal/pkgsite-cli/module.go
  • M cmd/internal/pkgsite-cli/package.go
  • M cmd/internal/pkgsite-cli/search.go
Change size: L
Delta: 7 files changed, 350 insertions(+), 47 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
  • requirement is not satisfiedkokoro-CI-Passes
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: pkgsite
Gerrit-Branch: master
Gerrit-Change-Id: Ic9c01b80fe6df6f673c6512b188907d8bd1580fa
Gerrit-Change-Number: 771240
Gerrit-PatchSet: 1
Gerrit-Owner: Hyang-Ah Hana Kim <hya...@gmail.com>
Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

kokoro (Gerrit)

unread,
Apr 27, 2026, 6:58:40 PM (2 days ago) Apr 27
to Hyang-Ah Hana Kim, goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, golang-co...@googlegroups.com
Attention needed from Hyang-Ah Hana Kim

kokoro voted kokoro-CI+1

Kokoro presubmit build finished with status: SUCCESS
Logs at: https://source.cloud.google.com/results/invocations/2ccbff6f-8742-4d10-bfd8-fc8042a7bd94

kokoro-CI+1
Open in Gerrit

Related details

Attention is currently required from:
  • Hyang-Ah Hana Kim
Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement satisfiedTryBots-Pass
    • requirement satisfiedkokoro-CI-Passes
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: pkgsite
    Gerrit-Branch: master
    Gerrit-Change-Id: Ic9c01b80fe6df6f673c6512b188907d8bd1580fa
    Gerrit-Change-Number: 771240
    Gerrit-PatchSet: 1
    Gerrit-Owner: Hyang-Ah Hana Kim <hya...@gmail.com>
    Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
    Gerrit-Reviewer: kokoro <noreply...@google.com>
    Gerrit-CC: kokoro <noreply...@google.com>
    Gerrit-Attention: Hyang-Ah Hana Kim <hya...@gmail.com>
    Gerrit-Comment-Date: Mon, 27 Apr 2026 22:58:36 +0000
    Gerrit-HasComments: No
    Gerrit-Has-Labels: Yes
    unsatisfied_requirement
    satisfied_requirement
    open
    diffy

    Jonathan Amsterdam (Gerrit)

    unread,
    Apr 28, 2026, 12:41:48 PM (17 hours ago) Apr 28
    to Hyang-Ah Hana Kim, goph...@pubsubhelper.golang.org, kokoro, golang...@luci-project-accounts.iam.gserviceaccount.com, golang-co...@googlegroups.com
    Attention needed from Hyang-Ah Hana Kim

    Jonathan Amsterdam added 5 comments

    File cmd/internal/pkgsite-cli/client/client.go
    Line 38, Patchset 1 (Latest):func Paginate[T any](startToken string, fetch func(token string) (*PaginatedResponse[T], error)) iter.Seq2[T, error] {
    Jonathan Amsterdam . unresolved

    This is doing the opposite of paginating. I'd just call it Items.

    Line 62, Patchset 1 (Latest):func PaginateAll[T any](startToken string, fetch func(token string) (*PaginatedResponse[T], error), limit int) ([]T, int, error) {
    Jonathan Amsterdam . unresolved

    You could add limit to Paginate, and use slices.Collect for this.

    Line 73, Patchset 1 (Latest): if resp.NextPageToken != "" && (limit == 0 || len(allItems) < limit) {
    Jonathan Amsterdam . unresolved

    I think this is wrong. You'd miss the items on the first page if there were only one.

    File cmd/internal/pkgsite-cli/module.go
    Line 54, Patchset 1 (Latest): items, total, err := client.PaginateAll("", fetch, m.effectiveLimit())
    Jonathan Amsterdam . unresolved

    Do we need the limit twice?

    File cmd/internal/pkgsite-cli/package.go
    Line 80, Patchset 1 (Latest): r, err := c.GetImportedBy(ctx, path, version, client.ImportedByOptions{

    Module: p.module,
    PaginationOptions: client.PaginationOptions{
    Limit: p.effectiveLimit(),
    Token: token,
    },
    Jonathan Amsterdam . unresolved

    Why is the call repeated?

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Hyang-Ah Hana Kim
    Submit Requirements:
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      • requirement satisfiedTryBots-Pass
      • requirement satisfiedkokoro-CI-Passes
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: pkgsite
      Gerrit-Branch: master
      Gerrit-Change-Id: Ic9c01b80fe6df6f673c6512b188907d8bd1580fa
      Gerrit-Change-Number: 771240
      Gerrit-PatchSet: 1
      Gerrit-Owner: Hyang-Ah Hana Kim <hya...@gmail.com>
      Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
      Gerrit-Reviewer: kokoro <noreply...@google.com>
      Gerrit-CC: Jonathan Amsterdam <j...@google.com>
      Gerrit-CC: kokoro <noreply...@google.com>
      Gerrit-Attention: Hyang-Ah Hana Kim <hya...@gmail.com>
      Gerrit-Comment-Date: Tue, 28 Apr 2026 16:41:44 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      unsatisfied_requirement
      satisfied_requirement
      open
      diffy

      Hyang-Ah Hana Kim (Gerrit)

      unread,
      Apr 28, 2026, 6:24:40 PM (11 hours ago) Apr 28
      to Hyang-Ah Hana Kim, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
      Attention needed from Hyang-Ah Hana Kim

      Hyang-Ah Hana Kim uploaded new patchset

      Hyang-Ah Hana Kim uploaded patch set #2 to this change.
      Following approvals got outdated and were removed:
      Open in Gerrit

      Related details

      Attention is currently required from:
      • Hyang-Ah Hana Kim
      Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        • requirement is not satisfiedkokoro-CI-Passes
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: newpatchset
        Gerrit-Project: pkgsite
        Gerrit-Branch: master
        Gerrit-Change-Id: Ic9c01b80fe6df6f673c6512b188907d8bd1580fa
        Gerrit-Change-Number: 771240
        Gerrit-PatchSet: 2
        unsatisfied_requirement
        open
        diffy

        kokoro (Gerrit)

        unread,
        Apr 28, 2026, 6:35:29 PM (11 hours ago) Apr 28
        to Hyang-Ah Hana Kim, goph...@pubsubhelper.golang.org, Jonathan Amsterdam, golang...@luci-project-accounts.iam.gserviceaccount.com, golang-co...@googlegroups.com
        Attention needed from Hyang-Ah Hana Kim

        kokoro voted kokoro-CI-1

        Kokoro presubmit build finished with status: FAILURE
        Logs at: https://source.cloud.google.com/results/invocations/cb15cc8b-3b39-4738-a959-8645074a0df3

        kokoro-CI-1
        Open in Gerrit

        Related details

        Attention is currently required from:
        • Hyang-Ah Hana Kim
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        • requirement is not satisfiedkokoro-CI-Passes
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: pkgsite
        Gerrit-Branch: master
        Gerrit-Change-Id: Ic9c01b80fe6df6f673c6512b188907d8bd1580fa
        Gerrit-Change-Number: 771240
        Gerrit-PatchSet: 2
        Gerrit-Owner: Hyang-Ah Hana Kim <hya...@gmail.com>
        Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
        Gerrit-Reviewer: kokoro <noreply...@google.com>
        Gerrit-CC: Jonathan Amsterdam <j...@google.com>
        Gerrit-CC: kokoro <noreply...@google.com>
        Gerrit-Attention: Hyang-Ah Hana Kim <hya...@gmail.com>
        Gerrit-Comment-Date: Tue, 28 Apr 2026 22:35:25 +0000
        Gerrit-HasComments: No
        Gerrit-Has-Labels: Yes
        unsatisfied_requirement
        open
        diffy

        Hyang-Ah Hana Kim (Gerrit)

        unread,
        Apr 28, 2026, 6:41:11 PM (11 hours ago) Apr 28
        to Hyang-Ah Hana Kim, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
        Attention needed from Hyang-Ah Hana Kim

        Hyang-Ah Hana Kim uploaded new patchset

        Hyang-Ah Hana Kim uploaded patch set #3 to this change.
        Following approvals got outdated and were removed:
        Open in Gerrit

        Related details

        Attention is currently required from:
        • Hyang-Ah Hana Kim
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        • requirement is not satisfiedkokoro-CI-Passes
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: newpatchset
        Gerrit-Project: pkgsite
        Gerrit-Branch: master
        Gerrit-Change-Id: Ic9c01b80fe6df6f673c6512b188907d8bd1580fa
        Gerrit-Change-Number: 771240
        Gerrit-PatchSet: 3
        unsatisfied_requirement
        open
        diffy

        kokoro (Gerrit)

        unread,
        Apr 28, 2026, 6:53:16 PM (10 hours ago) Apr 28
        to Hyang-Ah Hana Kim, goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, Jonathan Amsterdam, golang-co...@googlegroups.com
        Attention needed from Hyang-Ah Hana Kim

        kokoro voted kokoro-CI-1

        Kokoro presubmit build finished with status: FAILURE

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Hyang-Ah Hana Kim
        Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        • requirement is not satisfiedkokoro-CI-Passes
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: pkgsite
        Gerrit-Branch: master
        Gerrit-Change-Id: Ic9c01b80fe6df6f673c6512b188907d8bd1580fa
        Gerrit-Change-Number: 771240
        Gerrit-PatchSet: 3
        Gerrit-Owner: Hyang-Ah Hana Kim <hya...@gmail.com>
        Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
        Gerrit-Reviewer: kokoro <noreply...@google.com>
        Gerrit-CC: Jonathan Amsterdam <j...@google.com>
        Gerrit-CC: kokoro <noreply...@google.com>
        Gerrit-Attention: Hyang-Ah Hana Kim <hya...@gmail.com>
        Gerrit-Comment-Date: Tue, 28 Apr 2026 22:53:13 +0000
        Gerrit-HasComments: No
        Gerrit-Has-Labels: Yes
        unsatisfied_requirement
        open
        diffy
        Reply all
        Reply to author
        Forward
        0 new messages