Skip to content

[Fix] Preserve key_alias and team_id metadata in /user/daily/activity/aggregated after key deletion or regeneration#20684

Merged
shivamrawat1 merged 6 commits intomainfrom
litellm_preserver_team_key_alias_after_key_regeneration_and_deletion
Feb 15, 2026
Merged

[Fix] Preserve key_alias and team_id metadata in /user/daily/activity/aggregated after key deletion or regeneration#20684
shivamrawat1 merged 6 commits intomainfrom
litellm_preserver_team_key_alias_after_key_regeneration_and_deletion

Conversation

@shivamrawat1
Copy link
Collaborator

@shivamrawat1 shivamrawat1 commented Feb 8, 2026

Relevant issues

Issue: When an API key is deleted or regenerated, the /user/daily/activity/aggregated endpoint returns null for key_alias and team_id in the api_key_breakdown metadata. The spend metrics (tokens, cost, requests) remain intact, but the identity metadata needed to attribute those metrics to a specific user or team is lost — breaking audit and cost-allocation workflows.

Cause: The get_api_key_metadata() function in common_daily_activity.py resolves key_alias and team_id by querying only the active litellm_verificationtoken table at request time. When a key is deleted, its row is removed from that table (moved to litellm_deletedverificationtoken), so the lookup returns nothing. When a key is regenerated, the old token hash is overwritten with a new one in-place, so the old hash — still referenced by historical spend records — no longer matches any row.

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have Added testing in the tests/litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Type

🐛 Bug Fix
✅ Test

Changes

Fix: Two changes were made. First, get_api_key_metadata() now falls back to the litellm_deletedverificationtoken table for any keys not found in the active table, retrieving the most recent deleted record. Second, regenerate_key_fn() now persists the old key record to the deleted table before replacing the token hash, ensuring the old hash and its metadata remain queryable for historical spend attribution.

@vercel
Copy link

vercel bot commented Feb 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Feb 15, 2026 1:03am

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 8, 2026

Greptile Overview

Greptile Summary

This PR fixes loss of key_alias and team_id in /user/daily/activity/aggregated when keys are deleted or regenerated.

  • common_daily_activity.get_api_key_metadata() now looks up token metadata from litellm_verificationtoken and falls back to litellm_deletedverificationtoken for missing hashes, so historical spend rows can still be attributed.
  • regenerate_key_fn() now persists the pre-regeneration key row into the deleted table before overwriting the token hash, so the old hash remains queryable for historical analytics.
  • Adds unit tests for active + deleted lookup, mixed cases, error handling, and an aggregated-activity integration assertion.

The changes integrate with the existing analytics aggregation flow (_aggregate_spend_recordsupdate_breakdown_metrics) by ensuring api_key_metadata is populated even for deleted/regenerated keys.

Confidence Score: 3/5

  • This PR is likely correct functionally but has a couple of failure-mode issues that can reintroduce the metadata-loss behavior under real DB errors.
  • The core approach (fallback lookup + persisting old key on regeneration) matches the stated bug and is covered by new tests, but both new code paths broadly swallow exceptions—especially the regeneration persistence step—so production failures can silently drop metadata or permanently lose the old-hash mapping.
  • litellm/proxy/management_endpoints/key_management_endpoints.py (regeneration persistence error handling) and litellm/proxy/management_endpoints/common_daily_activity.py (deleted-table query exception handling)

Important Files Changed

Filename Overview
litellm/proxy/management_endpoints/common_daily_activity.py Adds deleted-keys fallback in get_api_key_metadata() to preserve key_alias/team_id for historical analytics; currently swallows all exceptions when querying deleted table, which can silently hide real failures.
litellm/proxy/management_endpoints/key_management_endpoints.py Persists current key row to deleted table before regenerating token hash and tightens a user_id check in list_keys; regeneration path ignores persistence failures, which can reintroduce the metadata-loss problem.
tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py Adds unit tests covering get_api_key_metadata() active/deleted fallback behavior and integration into aggregated activity; tests largely mock Prisma calls and validate metadata propagation.

Sequence Diagram

sequenceDiagram
  autonumber
  participant Client
  participant API as /user/daily/activity/aggregated
  participant Daily as common_daily_activity._aggregate_spend_records
  participant Active as litellm_verificationtoken
  participant Deleted as litellm_deletedverificationtoken
  participant Key as /key/*/regenerate

  Client->>API: GET aggregated activity
  API->>Daily: query spend rows + aggregate
  Daily->>Active: find_many(token in api_keys)
  alt some keys missing
    Daily->>Deleted: find_many(token in missing_keys, order deleted_at desc)
    Deleted-->>Daily: deleted key rows
  end
  Daily-->>API: SpendAnalytics response with key_alias/team_id metadata
  API-->>Client: api_key_breakdown includes metadata

  Client->>Key: POST regenerate key
  Key->>Deleted: create_many(old key row persisted)
  Key->>Active: update(token hash to new)
  Key-->>Client: new key + updated params
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +346 to +363
try:
deleted_key_records = (
await prisma_client.db.litellm_deletedverificationtoken.find_many(
where={"token": {"in": list(missing_keys)}},
order={"deleted_at": "desc"},
)
)
# Use the most recent deleted record for each token (ordered by deleted_at desc)
for k in deleted_key_records:
if k.token not in result:
result[k.token] = {
"key_alias": k.key_alias,
"team_id": k.team_id,
}
except Exception:
verbose_proxy_logger.debug(
"Failed to fetch deleted key metadata for missing keys"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Broad exception hides bugs

get_api_key_metadata() swallows any exception when querying litellm_deletedverificationtoken and just returns partial metadata. This can silently mask real issues (e.g., query/schema errors) and lead to key_alias/team_id unexpectedly staying null with no actionable signal. At minimum, log the exception object (or re-raise non-“table missing” errors) so production failures don’t get silently ignored.

Comment on lines +3316 to +3328
# Save the old key record to deleted table before regeneration
# This preserves key_alias and team_id metadata for historical spend records
try:
await _persist_deleted_verification_tokens(
keys=[_key_in_db],
prisma_client=prisma_client,
user_api_key_dict=user_api_key_dict,
litellm_changed_by=litellm_changed_by,
)
except Exception:
verbose_proxy_logger.debug(
"Failed to persist old key record to deleted table during regeneration"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Regeneration can lose history

In regenerate_key_fn(), persisting the old key into the deleted table is wrapped in except Exception and failures are ignored. If _persist_deleted_verification_tokens() fails (DB outage, constraint violation, etc.), the function proceeds to overwrite the token hash anyway, permanently losing the old hash→metadata mapping that this PR is trying to preserve. This needs to fail the regeneration (or otherwise guarantee persistence) when the persistence step can’t be completed.

@shivamrawat1
Copy link
Collaborator Author

@greptile review given the previous comments have been resolved in the new commit

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 8, 2026

Greptile Overview

Greptile Summary

This PR fixes a critical data loss issue where key_alias and team_id metadata disappeared from activity reports after API key deletion or regeneration, breaking audit trails and cost attribution.

Key changes:

  • get_api_key_metadata() now queries litellm_deletedverificationtoken as a fallback when keys aren't found in the active table, ensuring historical metadata remains accessible
  • regenerate_key_fn() persists the old key record to the deleted table before updating the token hash, preventing metadata loss during regeneration
  • The regeneration flow correctly fails if persistence fails, avoiding permanent data loss
  • Comprehensive test coverage validates active keys, deleted keys, mixed scenarios, error handling, and end-to-end aggregation

Previous thread resolution:

  • The "regeneration can lose history" concern has been addressed — the persistence call is NOT wrapped in exception handling, so failures will abort the regeneration
  • The broad exception handling in get_api_key_metadata() remains and could mask real errors (e.g., schema issues, query errors) behind a warning log

Confidence Score: 4/5

  • Safe to merge with one minor concern about error visibility
  • The core fix is sound and well-tested. Regeneration correctly aborts on persistence failure (addressing previous concerns). The fallback query adds minimal overhead to an analytics endpoint. Score reduced by 1 point due to overly broad exception handling that could hide legitimate database errors.
  • common_daily_activity.py - consider improving error handling specificity

Important Files Changed

Filename Overview
litellm/proxy/management_endpoints/common_daily_activity.py Adds fallback to deleted keys table when fetching API key metadata, with broad exception handling that could mask real errors
litellm/proxy/management_endpoints/key_management_endpoints.py Persists old key record before regeneration to preserve metadata; includes unrelated fix for user_id comparison
tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py Comprehensive unit tests covering active keys, deleted keys, mixed scenarios, error handling, and end-to-end aggregation

Sequence Diagram

sequenceDiagram
    participant User
    participant API as /user/daily/activity/aggregated
    participant GDAA as get_daily_activity_aggregated
    participant GAKM as get_api_key_metadata
    participant ActiveDB as litellm_verificationtoken
    participant DeletedDB as litellm_deletedverificationtoken
    
    User->>API: Request activity report
    API->>GDAA: Fetch aggregated data
    GDAA->>GAKM: Get metadata for api_keys
    
    GAKM->>ActiveDB: Query active keys
    ActiveDB-->>GAKM: Return found keys
    
    alt Some keys not found
        GAKM->>DeletedDB: Query deleted keys (fallback)
        DeletedDB-->>GAKM: Return deleted key metadata
    end
    
    GAKM-->>GDAA: Return complete metadata
    GDAA-->>API: Activity data with preserved metadata
    API-->>User: Report with key_alias and team_id

    Note over User,DeletedDB: Key Regeneration Flow

    participant RegAPI as /key/regenerate
    participant RegFn as regenerate_key_fn
    participant Persist as _persist_deleted_verification_tokens
    
    User->>RegAPI: Regenerate key
    RegAPI->>RegFn: Process regeneration
    RegFn->>Persist: Save old key to deleted table
    
    alt Persist fails
        Persist--XRegFn: Raise exception
        RegFn--XRegAPI: Abort regeneration
        RegAPI--XUser: Error - metadata not lost
    else Persist succeeds
        Persist-->>RegFn: Success
        RegFn->>ActiveDB: Update token hash
        ActiveDB-->>RegFn: Updated
        RegFn-->>User: New token
    end
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

admin_team_ids = None

if not user_id and user_api_key_dict.user_role not in [
if user_id is None and user_api_key_dict.user_role not in [
Copy link
Contributor

Choose a reason for hiding this comment

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

Unrelated to PR scope

This fix (user_id is None vs not user_id) is correct but unrelated to preserving key metadata after deletion/regeneration.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

does not change the flow.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Where did this change come from? looks like you're about to cause a regression: #20623

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed it

Copy link
Collaborator

@yuneng-jiang yuneng-jiang left a comment

Choose a reason for hiding this comment

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

lgtm

@shivamrawat1 shivamrawat1 merged commit 47048d1 into main Feb 15, 2026
15 of 46 checks passed
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.

2 participants