Skip to content

Some changes to the client - #11

Open
loganintech wants to merge 2 commits into
mainfrom
logan/some-fixes
Open

Some changes to the client#11
loganintech wants to merge 2 commits into
mainfrom
logan/some-fixes

Conversation

@loganintech

Copy link
Copy Markdown
Contributor

No description provided.

Comment thread pkg/client/client.go
return nil, false, nil
}
return nil, false, fmt.Errorf(
"baton-discord: failed to get member %s of guild %s: %w", userID, guildID, err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: Member is the one method this PR adds and the one that doesn't go through wrapErr, so its failures still reach the SDK as codes.Unknown. Since it's called per-overwrite inside channelBuilder.Grants, a 403 or a 5xx here is exactly the case the new classification was meant to distinguish. Use wrapErr(err, "baton-discord: failed to get member %s of guild %s", userID, guildID).

Comment thread pkg/client/client.go
if statusCode >= 500 && statusCode < 600 {
return codes.Unavailable
}
return codes.Unknown

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: httpStatus returns 0 whenever the error carries no rest.Error response, so every transport-level failure — the 60s http.Client{Timeout: requestTimeout} firing, connection resets, DNS/TLS errors, context.DeadlineExceeded, context.Canceled — falls through to codes.Unknown. By this function's own reasoning that leaves the most transient class of failure outside the retryer's Unavailable/DeadlineExceeded set, and turns a cancelled sync into an unknown failure. Consider checking errors.Is(err, context.DeadlineExceeded)DeadlineExceeded, context.CanceledCanceled, and mapping a remaining statusCode == 0 to Unavailable.

Comment thread pkg/client/client.go
"Server Members Intent on the bot application: %w", guildID, err)
}
return nil, "", fmt.Errorf("baton-discord: failed to list members of guild %s: %w", guildID, err)
return nil, "", wrapErr(err, "baton-discord: failed to list members of guild %s", guildID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: the fallthrough on the line below moved to wrapErr, but the IsForbidden branch just above it still uses plain fmt.Errorf. A missing Server Members Intent is the most likely real-world 403 in this connector, and it now reaches the SDK as codes.Unknown rather than PermissionDenied. Wrapping it while keeping the guidance text preserves both: uhttp.WrapErrors(codes.PermissionDenied, msg, fmt.Errorf(msg+": %w", err)).

Comment thread pkg/connector/channels.go
return nil, nil, err
}
if !stillAMember {
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: this drops a permission overwrite from the sync with no record that it happened. client.IsNotFound matches more than a departed member — it also matches the UnknownGuild/UnknownUser/UnknownChannel JSON codes — so a probe that 404s for a reason other than "left the server" silently removes a real grant, which C1 reads as access revoked. A ctxzap.Extract(ctx).Debug(...) (or Warn) naming the channel and target here would keep the omission attributable.

Comment thread pkg/client/client.go
return nil
}
msg := fmt.Sprintf(format, args...)
return uhttp.WrapErrors(statusCodeFor(err), msg, fmt.Errorf("%s: %w", msg, err))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: msg is passed twice — once as the WrapErrors message and once more inside the wrapped error — so the rendered error most likely reads "...failed to list roles of guild X: ...failed to list roles of guild X: <cause>". Passing the bare cause (uhttp.WrapErrors(statusCodeFor(err), msg, err)) keeps both the status code and the unwrappability without the duplicated prefix. Worth confirming against the actual output before changing.

Comment thread pkg/connector/users.go
"is_bot": member.User.Bot,
profileKeyUserID: member.User.ID.String(),
"username": member.User.Username,
"is_bot": member.User.Bot,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: dropping guild_id from the profile and re-basing created_at on the account snowflake are both sound (nothing in this repo reads either), but they change data C1 already ingested for every user — an attribute mapping or report built on guild_id, or anything reading created_at as a join date, changes meaning on the next sync. The reasoning is in the README, but the PR body is empty and the title is "Some changes to the client"; please state the user-visible change in the PR description so it is visible at deploy time.

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

Connector PR Review: Some changes to the client

Blocking Issues: 0 | Suggestions: 6 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base fb6b959dc293.
Review mode: incremental since d8e693a2
View review run

Review Summary

The only new commit since the last review is 468da73, a bump of the indirect dependency github.com/cockroachdb/swiss (20251224182025 to 20260820225851) that widens the vendored runtime shim's build tag from !go1.27 to !go1.28. go.mod, go.sum, and vendor/modules.txt are consistent, the module's go.mod hash is unchanged, and this repo builds on go 1.25.2, so there is no behavior change today. The incremental artifact dropped two vendored paths (vendor/github.com/cockroachdb/swiss/runtime_go1.20.go, vendor/modules.txt), so I reviewed those from the full PR diff, which I also re-scanned end to end for security and correctness. No new issues found; none of the six prior suggestions have been addressed yet, so they are carried forward below unchanged.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

All six carried over from the previous review — still present at the current head.

  • pkg/client/client.go:135statusCodeFor returns Unknown for every error without an HTTP response (request timeout, connection reset, context.DeadlineExceeded/Canceled), leaving the most transient failures outside the retryer's set.
  • pkg/client/client.go:268 — the newly added Member is the only client method that skips wrapErr, so its failures still reach the SDK as codes.Unknown.
  • pkg/client/client.go:226 — the IsForbidden branch in MembersPage was left on plain fmt.Errorf while the line below it moved to wrapErr; a missing Server Members Intent arrives as Unknown instead of PermissionDenied.
  • pkg/connector/channels.go:226 — the departed-member skip drops an overwrite with no log, and IsNotFound matches more than a member-404, so an unexpected 404 silently removes a real grant.
  • pkg/client/client.go:145wrapErr passes msg both as the WrapErrors message and inside the wrapped error, duplicating the prefix in the rendered string, since uhttp.WrapErrors joins the status error with the errors it is given.
  • pkg/connector/users.go:72 — removing guild_id from the user profile and re-basing created_at on the account snowflake are downstream-visible changes to already-synced data, but the PR body is empty.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In pkg/client/client.go:
- Around line 116-136: statusCodeFor derives its code solely from httpStatus(err), which
  returns 0 whenever the error chain carries no rest.Error with a non-nil Response. That
  means transport-level failures - the client request timeout firing, connection resets,
  DNS/TLS errors, context.DeadlineExceeded, context.Canceled - all map to codes.Unknown.
  Per the function's own comment the provisioning retryer only fires on Unavailable and
  DeadlineExceeded, so the most transient class of failure is never retried. Add explicit
  handling before the status switch: errors.Is(err, context.DeadlineExceeded) maps to
  codes.DeadlineExceeded, errors.Is(err, context.Canceled) maps to codes.Canceled, and
  consider mapping a remaining statusCode of 0 to codes.Unavailable rather than codes.Unknown.
- Around line 262-269: Client.Member is the only method added by this PR that does not use
  wrapErr, so its errors reach the SDK as codes.Unknown. It is called per-overwrite from
  channelBuilder.Grants, so a 403 or 5xx there loses exactly the classification this PR
  adds. Replace the fmt.Errorf return with a wrapErr call using the same message and the
  userID and guildID arguments. Keep the IsNotFound(err) early return above it unchanged.
- Around line 220-226: the IsForbidden(err) branch in MembersPage still uses plain
  fmt.Errorf while the fallthrough directly below it was converted to wrapErr. A missing
  Server Members Intent is the most likely real 403 in this connector and now surfaces as
  codes.Unknown instead of codes.PermissionDenied. Wrap it while preserving the guidance
  text, for example by building the message string first and returning uhttp.WrapErrors
  with codes.PermissionDenied, that message, and the wrapped error.
- Around line 140-146: wrapErr passes msg twice - once as the WrapErrors message argument
  and again inside the fmt.Errorf that wraps err. uhttp.WrapErrors builds a status error
  from the code and message and joins it with the errors it is given, so the rendered
  string repeats the prefix. Pass the underlying error through without re-prefixing it:
  return uhttp.WrapErrors(statusCodeFor(err), msg, err).

In pkg/connector/channels.go:
- Around line 220-228: the departed-member skip silently drops the overwrite. client.IsNotFound
  matches any 404 from the member probe, not only unknown-member, so an unexpected 404
  (wrong guild ID, a Discord-side path change) quietly removes a real grant instead of
  failing the sync. Log the skip at Warn with the guild and user IDs so the drop is
  observable, and narrow the not-found detection to Discord's unknown-member JSON error code
  (rest.JSONErrorCodeUnknownMember) if the client can distinguish it, so other 404s
  propagate as errors.

In pkg/connector/users.go:
- Around line 69-73 and 105-106: removing guild_id from the user profile and changing
  created_at from the per-server join date to the account snowflake timestamp are
  downstream-visible changes to data that has already been synced. The README now explains
  the reasoning, but the PR body is still empty. Describe both changes in the PR description
  so the behavior shift on existing installs is reviewable and not discovered after deploy.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No blocking issues found.

swiss uses go:linkname into runtime internals, so it gates that file
behind a manually bumped build-tag range. The vendored version was
tagged (go1.20 && !go1.27), which excluded the file under go1.27 and
left hashFn/getRuntimeHasher/fastrand64 undefined at build time.

Bump to the pseudo-version tagged (go1.20 && !go1.28) and re-vendor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No blocking issues found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant