Skip to content
Open

update #3582

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: "CodeQL Advanced"

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: '20 10 * * 2'

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
security-events: write
packages: read
actions: read
contents: read

strategy:
fail-fast: false
matrix:
include:
# JavaScript/TypeScript explicitly skips compilation
- language: javascript-typescript
build-mode: autobuild

# C# compiles dynamically via autobuild execution
- language: javascript-typescipt
build-mode: autobuild

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

# Initializes the CodeQL tools with optional matrix keys
- name: Initialize CodeQL
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}

# Runs compilation rules dynamically ONLY when explicitly set in matrix properties
- name: Autobuild
if: matrix.build-mode == 'autobuild'
uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
category: "/language:${{ matrix.language }}"
51 changes: 51 additions & 0 deletions .github/workflows/codeqlaxe.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: "CodeQL Advanced"

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: '20 10 * * *'

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
security-events: write
packages: read
actions: read
contents: read

strategy:
fail-fast: false
matrix:
include:
# JavaScript/TypeScript explicitly skips compilation
- language: javascript-typescript
build-mode: none

# C# compiles dynamically via autobuild execution
- language: csharp
build-mode: manual

steps:
- name: Checkout repository
uses: actions/checkout # v4.2.2

# Initializes the CodeQL tools with optional matrix keys
- name: Initialize CodeQL
uses: github/codeql-action/init # v4.38.3
with:
languages: ${{ matrix.language }}

# Runs compilation rules dynamically ONLY when explicitly set in matrix properties
- name: Autobuild
if: matrix.build-mode == 'autobuild'
uses: github/codeql-action/autobuild # v4.38.3

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze # v4.38.3
with:
category: "/language:${{ matrix.language }}"
133 changes: 109 additions & 24 deletions pkg/errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
stderrors "errors"
"fmt"
"net/http"
"slices"
"strings"
"sync"
"time"

"github.com/github/github-mcp-server/v2/pkg/sanitize"
Expand All @@ -21,7 +23,7 @@ type GitHubAPIError struct {
Err error `json:"-"`
}

// NewGitHubAPIError creates a new GitHubAPIError with the provided message, response, and error.
// newGitHubAPIError creates a new GitHubAPIError with the provided message, response, and error.
func newGitHubAPIError(message string, resp *github.Response, err error) *GitHubAPIError {
return &GitHubAPIError{
Message: message,
Expand All @@ -31,7 +33,20 @@ func newGitHubAPIError(message string, resp *github.Response, err error) *GitHub
}

func (e *GitHubAPIError) Error() string {
return fmt.Errorf("%s: %w", e.Message, e.Err).Error()
if e == nil {
return ""
}
if e.Err == nil {
return e.Message
}
return fmt.Sprintf("%s: %v", e.Message, e.Err)
}

func (e *GitHubAPIError) Unwrap() error {
if e == nil {
return nil
}
return e.Err
}

type GitHubGraphQLError struct {
Expand All @@ -47,7 +62,20 @@ func newGitHubGraphQLError(message string, err error) *GitHubGraphQLError {
}

func (e *GitHubGraphQLError) Error() string {
return fmt.Errorf("%s: %w", e.Message, e.Err).Error()
if e == nil {
return ""
}
if e.Err == nil {
return e.Message
}
return fmt.Sprintf("%s: %v", e.Message, e.Err)
}

func (e *GitHubGraphQLError) Unwrap() error {
if e == nil {
return nil
}
return e.Err
}

type GitHubRawAPIError struct {
Expand All @@ -65,11 +93,26 @@ func newGitHubRawAPIError(message string, resp *http.Response, err error) *GitHu
}

func (e *GitHubRawAPIError) Error() string {
return fmt.Errorf("%s: %w", e.Message, e.Err).Error()
if e == nil {
return ""
}
if e.Err == nil {
return e.Message
}
return fmt.Sprintf("%s: %v", e.Message, e.Err)
}

func (e *GitHubRawAPIError) Unwrap() error {
if e == nil {
return nil
}
return e.Err
}

type GitHubErrorKey struct{}

type GitHubCtxErrors struct {
mu sync.Mutex
api []*GitHubAPIError
graphQL []*GitHubGraphQLError
raw []*GitHubRawAPIError
Expand All @@ -80,13 +123,13 @@ func ContextWithGitHubErrors(ctx context.Context) context.Context {
if ctx == nil {
ctx = context.Background()
}
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
// If the context already has GitHubCtxErrors, we just empty the slices to start fresh
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok && val != nil {
val.mu.Lock()
val.api = []*GitHubAPIError{}
val.graphQL = []*GitHubGraphQLError{}
val.raw = []*GitHubRawAPIError{}
val.mu.Unlock()
} else {
// If not, we create a new GitHubCtxErrors and set it in the context
ctx = context.WithValue(ctx, GitHubErrorKey{}, &GitHubCtxErrors{})
}

Expand All @@ -95,24 +138,39 @@ func ContextWithGitHubErrors(ctx context.Context) context.Context {

// GetGitHubAPIErrors retrieves the slice of GitHubAPIErrors from the context.
func GetGitHubAPIErrors(ctx context.Context) ([]*GitHubAPIError, error) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
return val.api, nil // return the slice of API errors from the context
if ctx == nil {
return nil, fmt.Errorf("context is nil")
}
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok && val != nil {
val.mu.Lock()
defer val.mu.Unlock()
return slices.Clone(val.api), nil
}
return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
}

// GetGitHubGraphQLErrors retrieves the slice of GitHubGraphQLErrors from the context.
func GetGitHubGraphQLErrors(ctx context.Context) ([]*GitHubGraphQLError, error) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
return val.graphQL, nil // return the slice of GraphQL errors from the context
if ctx == nil {
return nil, fmt.Errorf("context is nil")
}
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok && val != nil {
val.mu.Lock()
defer val.mu.Unlock()
return slices.Clone(val.graphQL), nil
}
return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
}

// GetGitHubRawAPIErrors retrieves the slice of GitHubRawAPIErrors from the context.
func GetGitHubRawAPIErrors(ctx context.Context) ([]*GitHubRawAPIError, error) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
return val.raw, nil // return the slice of raw API errors from the context
if ctx == nil {
return nil, fmt.Errorf("context is nil")
}
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok && val != nil {
val.mu.Lock()
defer val.mu.Unlock()
return slices.Clone(val.raw), nil
}
return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
}
Expand All @@ -134,27 +192,41 @@ func NewGitHubGraphQLErrorToCtx(ctx context.Context, message string, err error)
}

func addGitHubAPIErrorToContext(ctx context.Context, err *GitHubAPIError) (context.Context, error) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
val.api = append(val.api, err) // append the error to the existing slice in the context
if ctx == nil {
return nil, fmt.Errorf("context is nil")
}
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok && val != nil {
val.mu.Lock()
val.api = append(val.api, err)
val.mu.Unlock()
return ctx, nil
}
return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
}

func addGitHubGraphQLErrorToContext(ctx context.Context, err *GitHubGraphQLError) (context.Context, error) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
val.graphQL = append(val.graphQL, err) // append the error to the existing slice in the context
if ctx == nil {
return nil, fmt.Errorf("context is nil")
}
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok && val != nil {
val.mu.Lock()
val.graphQL = append(val.graphQL, err)
val.mu.Unlock()
return ctx, nil
}
return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
}

func addRawAPIErrorToContext(ctx context.Context, err *GitHubRawAPIError) (context.Context, error) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
if ctx == nil {
return nil, fmt.Errorf("context is nil")
}
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok && val != nil {
val.mu.Lock()
val.raw = append(val.raw, err)
val.mu.Unlock()
return ctx, nil
}

return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
}

Expand All @@ -165,7 +237,8 @@ func NewGitHubAPIErrorResponse(ctx context.Context, message string, resp *github
_, _ = addGitHubAPIErrorToContext(ctx, apiErr) // Explicitly ignore error for graceful handling
}

if rateLimitErr, ok := stderrors.AsType[*github.RateLimitError](err); ok {
var rateLimitErr *github.RateLimitError
if stderrors.As(err, &rateLimitErr) && rateLimitErr != nil {
resetTime := rateLimitErr.Rate.Reset.Time
if !resetTime.IsZero() {
retryIn := time.Until(resetTime).Round(time.Second)
Expand All @@ -178,7 +251,8 @@ func NewGitHubAPIErrorResponse(ctx context.Context, message string, resp *github
"%s: GitHub API rate limit exceeded. Wait before retrying.", message))
}

if abuseErr, ok := stderrors.AsType[*github.AbuseRateLimitError](err); ok {
var abuseErr *github.AbuseRateLimitError
if stderrors.As(err, &abuseErr) && abuseErr != nil {
if abuseErr.RetryAfter != nil {
retryAfter := abuseErr.RetryAfter.Round(time.Second)
if retryAfter > 0 {
Expand All @@ -198,15 +272,15 @@ func NewGitHubAPIErrorResponse(ctx context.Context, message string, resp *github
// including request or response metadata from the underlying HTTP exchange.
func formatGitHubValidationError(resp *github.Response, err error) error {
var ghErr *github.ErrorResponse
if !stderrors.As(err, &ghErr) {
if !stderrors.As(err, &ghErr) || ghErr == nil {
return err
}

statusCode := 0
if ghErr.Response != nil {
statusCode = ghErr.Response.StatusCode
}
if statusCode == 0 && resp != nil {
if statusCode == 0 && resp != nil && resp.Response != nil {
statusCode = resp.StatusCode
}
if statusCode != http.StatusUnprocessableEntity {
Expand Down Expand Up @@ -280,7 +354,12 @@ func NewGitHubRawAPIErrorResponse(ctx context.Context, message string, resp *htt
// but returns an unexpected HTTP status code. It creates a synthetic error from the
// status code and response body, then records it in context for observability tracking.
func NewGitHubAPIStatusErrorResponse(ctx context.Context, message string, resp *github.Response, body []byte) *mcp.CallToolResult {
err := fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body))
var err error
if resp != nil && resp.Response != nil {
err = fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body))
} else {
err = fmt.Errorf("unexpected status: %s", string(body))
}
return NewGitHubAPIErrorResponse(ctx, message, resp, err)
}

Expand All @@ -306,6 +385,9 @@ type StructuredResolutionError struct {
// Error implements the error interface; the message is the JSON body so that the
// downstream tool result also carries the structured payload as plain text.
func (e *StructuredResolutionError) Error() string {
if e == nil {
return `{"error":"unknown"}`
}
b, err := json.Marshal(e)
if err != nil {
return fmt.Sprintf(`{"error":%q,"name":%q}`, e.Kind, e.Name)
Expand All @@ -326,5 +408,8 @@ func NewStructuredResolutionError(kind, name, hint string, candidates []any) *St
// NewStructuredResolutionErrorResponse returns an mcp.CallToolResult whose text body
// is the JSON-serialised StructuredResolutionError, suitable for agent self-correction.
func NewStructuredResolutionErrorResponse(err *StructuredResolutionError) *mcp.CallToolResult {
if err == nil {
return utils.NewToolResultError(`{"error":"unknown"}`)
}
return utils.NewToolResultError(err.Error())
}
Loading