Tencent/WeKnora · error
list docs for book %d: %w
Error message
list docs for book %d: %w
What it means
In walk, after a book ID parses successfully the connector calls cli.ListBookDocs to enumerate the book's documents. This error wraps any failure of that upstream Yuque API call, identifying which book ID failed. The cause is preserved with %w.
Source
Thrown at internal/datasource/connector/yuque/connector.go:174
) ([]types.FetchedItem, *yuqueCursor, error) {
cfg, err := parseYuqueConfig(config)
if err != nil {
return nil, nil, err
}
cli := newClient(cfg)
newCursor := &yuqueCursor{LastSyncTime: time.Now(), BookDocTimes: make(map[string]map[string]string)}
var out []types.FetchedItem
for _, bookIDStr := range resourceIDs {
bookID, err := strconv.ParseInt(bookIDStr, 10, 64)
if err != nil {
return nil, nil, fmt.Errorf("invalid book id %q: %w", bookIDStr, err)
}
docs, err := cli.ListBookDocs(ctx, bookID)
if err != nil {
return nil, nil, fmt.Errorf("list docs for book %d: %w", bookID, err)
}
currentDocs := make(map[string]bool)
newCursor.BookDocTimes[bookIDStr] = make(map[string]string)
var skippedType, skippedDraft, kept int
var sampleSkipType, sampleSkipDraft string
for _, d := range docs {
// Empty type/status is treated as acceptable — forward-compat with
// API variations that omit the field.
if d.Type != "" && d.Type != "Doc" {
skippedType++
if sampleSkipType == "" {
sampleSkipType = fmt.Sprintf("id=%d type=%q title=%q", d.ID, d.Type, d.Title)
}
continue
}
if d.Status != "" && d.Status != "1" {View on GitHub (pinned to 988cbb0330)
Solutions
- Verify the book still exists and is readable with curl https://api.yuque.com/api/v2/repos/<bookID>/docs using the configured token
- Remove or update stale book IDs in ResourceIDs if the repo was deleted/moved
- Re-issue the token with read access to all configured books (or an org-wide token)
- Retry on transient network/5xx/429 errors; reduce poll frequency if rate limited
Example fix
// before
docs, err := cli.ListBookDocs(ctx, bookID)
if err != nil {
return nil, nil, fmt.Errorf("list docs for book %d: %w", bookID, err)
}
// after
docs, err := cli.ListBookDocs(ctx, bookID)
if err != nil {
return nil, nil, fmt.Errorf("list docs for book %d: %w", bookID, err)
}
// plus caller-side: validate book IDs via a permission check before configuring them Defensive patterns
Strategy: try-catch
Validate before calling
// Verify each configured book is readable before syncing: // GET https://api.yuque.com/api/v2/repos/<bookID> with the token // non-200 => remove or fix that book ID in ResourceIDs
Type guard
var apiErr *yuque.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == 404 {
// book deleted or inaccessible: drop it from ResourceIDs and resync
} Try / catch
items, cur, err := ds.Fetch(ctx, cfg, cursor)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || isTransient(err) {
return retryWithBackoff(ctx)
}
return fmt.Errorf("incremental fetch failed: %w", err)
} Prevention
- Periodically re-verify configured book IDs still exist and are readable
- Use a token with access to all configured books (org-wide preferred)
- Handle 404 by flagging stale book IDs instead of failing the whole sync
- Back off on 429/5xx and cap concurrent book fetches
When it happens
Trigger: ListBookDocs(ctx, bookID) errors for a configured, numerically valid book ID — book deleted or access revoked, token lacks scope on that repo, network failure, or API rate limiting during a multi-book walk.
Common situations: Book was deleted or moved to another group after being configured; token belongs to a user/organization no longer having read access to the repo; 404 from Yuque for private books the token can't see.
Related errors
- list team repos: %w
- list personal repos: %w
- get block children for %s: %w
- yuque api error: status=%d msg=%s
- yuque api error: status=%d body=%s
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/7664501eb67b2ed5.
Report an issue: GitHub.