{"record":{"id":"55a969c433a60f37","repo":"owasp-amass/amass","slug":"s-stats-status-s-error-s","errorCode":null,"errorMessage":"%s/stats: status=%s error=%s","messagePattern":"(.+?)/stats: status=(.+?) error=(.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/api/client/v1/client.go","lineNumber":180,"sourceCode":"\t\treturn fmt.Errorf(\"terminateSession: status=%s error=%s\", resp.Status, msg)\n\t}\n\treturn nil\n}\n\n// Retrieves statistics for the session associated with the provided token.\nfunc (c *Client) SessionStats(ctx context.Context, token uuid.UUID) (*et.SessionStats, error) {\n\tresp, err := amasshttp.RequestWebPage(ctx, c.httpClient,\n\t\t&amasshttp.Request{URL: c.base + \"/sessions/\" + token.String() + \"/stats\"})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tmsg, err := readJSONError(resp.Body)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%s/stats: status=%s\", token.String(), resp.Status)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"%s/stats: status=%s error=%s\", token.String(), resp.Status, msg)\n\t}\n\n\tvar st et.SessionStats\n\tif err := json.Unmarshal([]byte(resp.Body), &st); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &st, nil\n}\n\n// Retrieves scope for the session associated with the provided token.\nfunc (c *Client) SessionScope(ctx context.Context, token uuid.UUID, atype oam.AssetType) ([]oam.Asset, error) {\n\tsessionID := token.String()\n\tatypestr := strings.ToLower(string(atype))\n\tu := fmt.Sprintf(\"%s/sessions/%s/scope/%s\", c.base, sessionID, atypestr)\n\tresp, err := amasshttp.RequestWebPage(ctx, c.httpClient, &amasshttp.Request{URL: u})\n\tif err != nil {\n\t\treturn nil, err\n\t}","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/owasp-amass/amass/blob/79299dce87b0085db0f2f4ef3e9c52cccb49f514/engine/api/client/v1/client.go#L162-L198","documentation":"Returned by Client.SessionStats when GET /api/v1/sessions/{token}/stats returns a non-200 status and the response body is a decodable JSON error, so the message includes the server's error text after 'error=': '<token>/stats: status=<status> error=<msg>'. It reports exactly why the server refused to return statistics for that session.","triggerScenarios":"Calling SessionStats(ctx, token) (or getStats) when the server rejects the request with a JSON error body: unknown or expired session token (404 with JSON message), auth rejection (401/403), or internal server error while computing stats (500).","commonSituations":"Session expired between TerminateSession/CreateSession cycles; token from a different engine instance; server-side auth misconfiguration; transient 500 while the server aggregates stats.","solutions":["Read the 'error=' text - it is the server's specific reason; for 'not found' re-create the session or pick a live token from ListSessions","For 401/403, fix the server's auth configuration or the client's credentials","For 5xx errors, retry with backoff and check server logs","Confirm the token belongs to the server instance you are querying","Add a HealthCheck gate before stats polling loops so a down server is detected early"],"exampleFix":"// before: polling stats without handling dead sessions\nfor {\n    st, err := client.SessionStats(ctx, token)\n    if err != nil {\n        log.Print(err) // loops forever on 404\n    }\n}\n\n// after: recreate the session when the server says it is gone\nst, err := client.SessionStats(ctx, token)\nif err != nil && strings.Contains(err.Error(), \"404\") {\n    token, err = client.CreateSession(ctx, cfg)\n    if err == nil {\n        st, err = client.SessionStats(ctx, token)\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Go: validate token is live before requesting stats\ntokens, err := c.ListSessions(ctx)\nif err != nil {\n    return err\n}\nlive := false\nfor _, t := range tokens {\n    if t == token {\n        live = true\n        break\n    }\n}\nif !live {\n    return fmt.Errorf(\"session %s is not active; recreate before querying stats\", token)\n}","typeGuard":null,"tryCatchPattern":"// Go\nst, err := c.SessionStats(ctx, token)\nif err != nil {\n    msg := err.Error()\n    switch {\n    case strings.Contains(msg, \"404\"):\n        // server told us the session is gone - recreate\n        token, err = c.CreateSession(ctx, cfg)\n        if err == nil {\n            st, err = c.SessionStats(ctx, token)\n        }\n    case strings.Contains(msg, \"401\"), strings.Contains(msg, \"403\"):\n        err = fmt.Errorf(\"auth rejected stats request: %w\", err)\n    default:\n        err = backoffRetryStats(ctx, c, token)\n    }\n}","preventionTips":["Always read the 'error=' suffix - the server explains the rejection","Handle session expiry proactively in long-running pollers (recreate on 404)","Keep auth configuration correct on the server side","Retry only transient 5xx statuses, with capped backoff"],"tags":["http","api","network","amass"],"backgroundTag":"http-error-response","analyzedSha":"79299dce87b0085db0f2f4ef3e9c52cccb49f514","analyzedAt":"2026-09-06T08:22:48.198Z","contentChangedAt":"2026-09-06T08:22:48.198Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}