Uploaded image for project: 'Project Quay'
  1. Project Quay
  2. PROJQUAY-10500

Quay 3.17 new UI add new label to immutable image tag hit error "Unable to complete request"

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Major Major
    • None
    • quay-v3.17.0
    • quay

      Problem Statement

      When label operations (create/delete) fail in the new React UI, users see a generic error message:

      Error displayed: "Unable to complete request - Undefined"

      Expected: Specific server error message explaining why the operation failed (e.g., "Cannot add label: tag is immutable", "Invalid label format", "Permission denied", etc.)

      This makes debugging and troubleshooting {}impossible{} for users.

      Visual Evidence

      See attached screenshot: Error3.jpg

      • Repository: quayqe/demo
      • Action: Attempting to add label to tag
      • Error shown: "Unable to complete request" + "Undefined"

      Root Cause Analysis

      Frontend Error Handling Bug

      File: web/src/components/labels/LabelsEditable.tsx:56-95

      The label error display code directly accesses error.error.message, which retrieves the generic Axios error message instead of the server's specific error message.

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml// Line 56-72: Create label error display
      if (errorCreatingLabels) {
        const errorCreatingLabelsMessage = (
          <>
            {Array.from(errorCreatingLabelsDetails.getErrors()).map(
              ([label, error]) => (
                <p key={label}>
                  Could not create label {label}: {error.error.message}  // ❌ WRONG
                </p>
              ),
            )}
          </>
        );
      }
      
      // Line 79-95: Delete label error display (same bug)
      if (errorDeletingLabels) {
        const errorDeletingLabelsMessage = (
          <>
            {Array.from(errorDeletingLabelsDetails.getErrors()).map(
              ([label, error]) => (
                <p key={label}>
                  Could not delete label {label}: {error.error.message}  // ❌ WRONG
                </p>
              ),
            )}
          </>
        );
      }
      

      Why This is Wrong

      ResourceError Structure:

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yamlclass ResourceError extends Error {
        error: AxiosError;        // Contains server response
        resource: string;         // e.g., "test=value"
        message: string;          // e.g., "Unable to create label"
      }
      

      AxiosError Structure:

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yamlinterface AxiosError {
        message: string;           // Generic: "Request failed with status code 400"
        code: string;              // e.g., "ERR_BAD_REQUEST"
        response: {                // ← SERVER ERROR DETAILS ARE HERE!
          status: number;
          data: {
            error_message?: string;  // ← REAL ERROR MESSAGE
            message?: string;
            detail?: string;
          }
        }
      }
      

      What Happens:

      • error.error.messageRequest failed with status code 400 (useless)
      • error.error.response.data.error_messageCannot add label: tag is immutable (useful!)

      The code accesses the wrong field, so users see the generic Axios error instead of the server's explanation.

      Correct Pattern Exists in Codebase

      Quay has a dedicated helper function for extracting server error messages:

      File: web/src/resources/ErrorHandling.ts:80-118

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yamlexport function getErrorMessage(error: AxiosError<ErrorResponse>) {
        // Check if server sent a response
        if (error.response) {
          // Extract server message from multiple possible fields
          const serverMessage =
            error.response.data?.detail ||
            error.response.data?.error_message ||
            error.response.data?.message ||
            error.response.data?.error_description ||
            (typeof error.response.data === 'string' ? error.response.data : null);
      
          // For 5xx errors, return generic message (security)
          if (error.response.status >= 500) {
            return 'an unexpected issue occurred...';
          }
      
          // For 4xx, return specific message
          if (serverMessage) {
            return serverMessage;  // ← THIS IS WHAT WE NEED!
          }
        }
      
        // Network errors
        if (error.code && ...) {
          return getNetworkError(error.code);
        }
      
        // Fallback
        return 'unable to make request';
      }
      

      This helper is used correctly in other parts of the UI (e.g., account creation errors - PROJQUAY-10418), but the Labels component doesn't use it.


      Impact Assessment

      Severity: MAJOR

      User Experience Impact

      • Users cannot diagnose why label operations fail
      • Generic "Undefined" error provides no actionable information
      • Increases support tickets and user frustration

      Affected Operations

      • Creating labels on manifests
      • Deleting labels from manifests
      • All label error scenarios show the same useless error

      Common Error Scenarios Hidden

      • Permission denied (user lacks write access)
      • Invalid label format (key/value validation failures)
      • Reserved label prefix (quay.* labels restricted)
      • Tag immutability conflicts
      • Database constraints violations
      • Network/server errors

      Debugging Difficulty

      • Support teams cannot help users troubleshoot
      • Developers cannot reproduce issues from user reports
      • No visibility into actual error cause

      Steps to Reproduce

      Scenario 1: Add Label to Immutable Tag

      1. Create repository quayqe/demo
      2. Push image and make tag stable immutable
      3. Navigate to Tags tab
      4. Click tag actions → "Edit labels"
      5. Try to add label test=value
      6. Observe: Error shows "Unable to complete request - Undefined"
      7. Expected: Specific error explaining immutability restriction

      Scenario 2: Invalid Label Format

      1. Navigate to any repository tag
      2. Click "Edit labels"
      3. Try to add label with invalid format: invalid label format
      4. Observe: Error shows "Unable to complete request - Undefined"
      5. Expected: Error shows "Invalid label format, must be key=value"

      Scenario 3: Permission Denied

      1. Navigate to repository where user has read-only access
      2. Try to add label to tag
      3. Observe: Error shows "Unable to complete request - Undefined"
      4. Expected: Error shows "Permission denied" or "Requires write access"

      Expected Behavior

      • Error message should display server's specific error message
      • Users should understand why the operation failed
      • Error should provide actionable guidance

      Actual Behavior

      • All errors show generic "Unable to complete request - Undefined"
      • No information about root cause
      • Users cannot troubleshoot

      Affected Code Files

      Frontend:

      • web/src/components/labels/LabelsEditable.tsx - Lines 56-95 (error display)
      • web/src/hooks/UseTagLabels.ts - Lines 45-67 (mutation hooks)
      • web/src/resources/TagResource.ts - Lines 248-270 (createLabel/deleteLabel)

      Error Handling (Existing Helper):

      • web/src/resources/ErrorHandling.ts - Lines 80-118 (getErrorMessage helper)

      Recommended Fix

      Fix: Use Existing Error Helper Function

      File: web/src/components/labels/LabelsEditable.tsx

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yamlimport { getErrorMessage } from 'src/resources/ErrorHandling';  // ✅ ADD THIS
      
      // Line 56-72: Fix create error display
      if (errorCreatingLabels) {
        const errorCreatingLabelsMessage = (
          <>
            {Array.from(errorCreatingLabelsDetails.getErrors()).map(
              ([label, error]) => (
                <p key={label}>
                  Could not create label {label}: {getErrorMessage(error.error)}  {/* ✅ CHANGED */}
                </p>
              ),
            )}
          </>
        );
        addAlert({
          variant: AlertVariant.Failure,
          title: `Could not create labels`,
          message: errorCreatingLabelsMessage,
        });
      }
      
      // Line 79-95: Fix delete error display
      if (errorDeletingLabels) {
        const errorDeletingLabelsMessage = (
          <>
            {Array.from(errorDeletingLabelsDetails.getErrors()).map(
              ([label, error]) => (
                <p key={label}>
                  Could not delete label {label}: {getErrorMessage(error.error)}  {/* ✅ CHANGED */}
                </p>
              ),
            )}
          </>
        );
        addAlert({
          variant: AlertVariant.Failure,
          title: `Could not delete labels`,
          message: errorDeletingLabelsMessage,
        });
      }
      

      Changes Required

      1. Import getErrorMessage helper from ErrorHandling.ts
      2. Replace error.error.message with getErrorMessage(error.error) (2 places)
      3. Add unit tests for error display

      Testing Requirements

      Manual Testing

      Test Case 1: Invalid Label Format

      1. Add label with invalid key: invalid label!@#
      2. Verify error shows: "Invalid label format, key must match ^[0-9A-Za-z/\\-_.]=.$"

      Test Case 2: Reserved Prefix

      1. Add label: quay.reserved=test
      2. Verify error shows: "Label has a reserved prefix"

      Test Case 3: Permission Denied

      1. Try to add label to repo with read-only access
      2. Verify error shows: "You do not have permission" or similar

      Test Case 4: Network Error

      1. Disconnect network
      2. Try to add label
      3. Verify error shows network-related message (not "Undefined")

      Test Case 5: Server Error (5xx)

      1. Simulate server error
      2. Verify error shows: "an unexpected issue occurred. Please try again or contact support"

      Automated Testing

      Unit Test: web/src/components/labels/LabelsEditable.test.tsx

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yamldescribe('LabelsEditable error handling', () => {
        it('displays server error message on create failure', async () => {
          // Mock API error with specific message
          mockAxios.post.mockRejectedValue({
            response: {
              status: 400,
              data: { error_message: 'Invalid label format' }
            }
          });
      
          // Try to create label
          render(<EditableLabels {...props} />);
          // ... user interaction ...
      
          // Verify specific error message is displayed
          expect(screen.getByText(/Invalid label format/)).toBeInTheDocument();
          expect(screen.queryByText(/Undefined/)).not.toBeInTheDocument();
        });
      
        it('displays server error message on delete failure', async () => {
          // Similar test for delete errors
        });
      
        it('handles network errors gracefully', async () => {
          // Test network error scenarios
        });
      });
      

      ❌ Broken Error Handling (This Issue)

      File: web/src/components/labels/LabelsEditable.tsx

      Labels component still uses the old broken pattern of accessing error.error.message directly.

      Regression risk: Other components may have the same bug and need auditing.


      Related Code Pattern Issues

      Other Components to Audit

      Search codebase for similar pattern:

      grep -r "error.error.message" web/src/
      

      Potentially affected:

      • Tag operations error handling
      • Repository settings error handling
      • Team management error handling
      • Any component using BulkOperationError<ResourceError>

      Preventive Measures

      Recommendation: Create ESLint rule to prevent this pattern:

      // .eslintrc.js
      rules: {
        'no-restricted-syntax': [
          'error',
          {
            selector: 'MemberExpression[object.property.name="error"][property.name="message"]',
            message: 'Use getErrorMessage() helper instead of error.error.message'
          }
        ]
      }
      

      Workaround (Until Fixed)

      For users encountering errors:

      1. Open browser DevTools (F12)
      2. Go to Network tab
      3. Retry the failed operation
      4. Find the failed API request (POST .../labels)
      5. Click on it → Response tab
      6. Read the error_message field for actual error

      For support teams:

      Ask users to provide:

      • Browser console errors
      • Network tab screenshots
      • Detailed steps to reproduce

      Acceptance Criteria

      Success metrics:

      • [ ] Error messages display server's specific error text
      • [ ] No "Undefined" errors shown to users
      • [ ] All error scenarios show actionable messages
      • [ ] Unit tests verify correct error extraction
      • [ ] Manual testing confirms improved UX
      • [ ] ESLint rule prevents future regressions (optional)

      Priority Justification

      Why MAJOR:

      1. User Impact: Affects all label operations across all repositories
      2. Debugging Difficulty: Users cannot troubleshoot without seeing real errors
      3. Support Burden: Increases support tickets and response time
      4. Regression Risk: Similar pattern may exist in other components
      5. Simple Fix: 2-line change with existing helper function

      Not CRITICAL because:

      • Workaround exists (browser DevTools)
      • Does not cause data loss
      • Does not block core functionality

      Reported by: Luffy Zhang (lzha1981)
      Generated by: Claude Code Automated Bug Analysis

        1. image-2026-02-05-15-11-09-169.png
          609 kB
          luffy zhang
        2. Error3.jpg
          357 kB
          luffy zhang

              rhn-support-bpratt Brady Pratt
              lzha1981 luffy zhang
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated: