Skip to content

fix: surface expired MinerU token errors during renovation#446

Open
Anionex wants to merge 2 commits into
mainfrom
fix/issue-420-mineru-auth-error
Open

fix: surface expired MinerU token errors during renovation#446
Anionex wants to merge 2 commits into
mainfrom
fix/issue-420-mineru-auth-error

Conversation

@Anionex

@Anionex Anionex commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Detect MinerU expired/invalid credential failures from HTTP 401/403, MinerU non-zero business responses, and failed parse task messages.
  • Return a specific actionable message: MinerU Token 无效或已过期,请在设置页更新 MinerU Token 后重试。
  • Make PPT renovation tasks fail with that specific message instead of continuing into an unclear empty-content failure.

Issue mapping

Fixes #420. The issue reports that PPT renovation showed an unclear generic failure when the MinerU key had expired. This PR maps those auth failures at the shared file parser layer and propagates them through the renovation task error_message that the existing frontend already displays.

Decision

I chose to fail-fast only for MinerU credential/auth errors during PPT renovation. Other parse failures keep the existing warning/empty-page behavior to avoid changing unrelated renovation behavior.

Verification

  • uv run python -m py_compile backend/services/file_parser_service.py backend/services/task_manager.py backend/tests/unit/test_file_parser_service.py backend/tests/unit/test_ppt_renovation_mineru_auth_error.py
  • uv run pytest backend/tests/unit/test_file_parser_service.py backend/tests/unit/test_ppt_renovation_mineru_auth_error.py -v
  • uv run pytest backend/tests/unit -v
  • uv run pytest backend/tests/integration -v -m "not requires_service"
  • git diff --check

Frontend / browser coverage

No browser pass was run because this is a backend-only error mapping change. The existing frontend already renders task.error_message for failed PPT renovation tasks; the new regression verifies that the backend task error_message contains the specific MinerU Token message.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request improves error handling for MinerU authentication failures by detecting invalid or expired tokens and surfacing clear, actionable messages to the user instead of generic network errors. It updates the file parser service and task manager to identify these credential issues and adds comprehensive unit and regression tests. The review feedback suggests enhancing the robustness of the error-parsing helpers by defensively handling potential AttributeError exceptions when reading response bodies or processing non-string error messages.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread backend/services/file_parser_service.py Outdated
Comment on lines +70 to +71
except ValueError:
body_text = (getattr(response, "text", "") or "").strip()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

If response is not a standard requests.Response object (e.g., a mock or a different exception response), calling response.json() will raise an AttributeError which is not caught by except ValueError:. This can crash the error handling path. Catching AttributeError and defensively converting the text attribute to a string ensures robust error extraction.

Suggested change
except ValueError:
body_text = (getattr(response, "text", "") or "").strip()
except (ValueError, AttributeError):
body_text = str(getattr(response, "text", "") or "").strip()

Comment thread backend/services/file_parser_service.py Outdated
if status_code in (401, 403):
return True

normalized = (text or "").lower()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If the API returns a non-string value for msg or message (such as an integer or a list of validation errors), text will not be a string. In that case, (text or "") will evaluate to that non-string object, and calling .lower() on it will raise an AttributeError. Converting the value to a string defensively prevents this potential crash.

Suggested change
normalized = (text or "").lower()
normalized = str(text or "").lower()

@Anionex
Anionex force-pushed the fix/issue-420-mineru-auth-error branch from cb1467b to 987672b Compare June 7, 2026 15:34
@Anionex

Anionex commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

Addressed the Gemini review suggestions in 987672b by defensively handling response-like objects without json() and non-string MinerU error payloads, with regression coverage added. @gemini-code-assist please re-review when available.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces specialized handling for MinerU authentication errors, ensuring that expired or invalid credentials surface as actionable, user-friendly messages rather than generic network failures. Changes include helper functions for error extraction and detection in file_parser_service.py, task failure propagation in task_manager.py, and comprehensive unit and regression tests. The review feedback suggests catching a broader Exception instead of only ValueError and AttributeError when calling response.json() to safely handle cases where response.json is not callable.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread backend/services/file_parser_service.py Outdated
Comment on lines +68 to +70
try:
body = response.json()
except (ValueError, AttributeError):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Catching only ValueError and AttributeError when calling response.json() can lead to unhandled exceptions such as TypeError if response.json is not callable (e.g., if it is None or a non-callable attribute). Catching a broader Exception ensures that any failure during JSON parsing safely falls back to extracting the error from response.text without bypassing the specific auth error detection.

Suggested change
try:
body = response.json()
except (ValueError, AttributeError):
try:
body = response.json()
except Exception:
References
  1. Use try-except blocks to handle potential errors and provide safe fallbacks.

@Anionex
Anionex force-pushed the fix/issue-420-mineru-auth-error branch from 987672b to 60258f8 Compare June 7, 2026 15:44
@Anionex

Anionex commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

Addressed the latest Gemini suggestion in 60258f8 by falling back on any response.json() failure and adding a non-callable json regression test. @gemini-code-assist please re-review when available.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces robust error handling and detection for MinerU authentication and credential failures. It adds helper functions to parse response errors, detect auth-related terms, and format user-friendly error messages. These checks are integrated into the file upload and polling processes, and task execution is updated to raise errors immediately upon authentication failure. Comprehensive unit and regression tests are also added. The feedback suggests enhancing the authentication failure term list with common English words like 'failed' and 'failure', and truncating raw response text fallbacks to prevent large HTML error pages from cluttering logs and the user interface.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +40 to +51
_MINERU_AUTH_FAILURE_TERMS = (
"expired",
"invalid",
"unauthorized",
"forbidden",
"denied",
"expire",
"过期",
"无效",
"失败",
"拒绝",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The _MINERU_AUTH_FAILURE_TERMS tuple is missing common English failure terms such as "failed", "fail", and "failure". If the MinerU API returns an error message like "auth failed" or "token validation failed", it will not be recognized as an authentication error because "failed" is not in the failure terms list (even though "auth" is in _MINERU_AUTH_TERMS).

Adding these terms will make the authentication error detection much more robust.

_MINERU_AUTH_FAILURE_TERMS = (
    "expired",
    "invalid",
    "unauthorized",
    "forbidden",
    "denied",
    "expire",
    "failed",
    "fail",
    "failure",
    "过期",
    "无效",
    "失败",
    "拒绝",
)

Comment thread backend/services/file_parser_service.py Outdated
Comment on lines +70 to +73
except Exception:
body_text = str(getattr(response, "text", "") or "").strip()
if body_text:
parts.append(body_text)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If the server returns a large HTML error page (e.g., a 502 Bad Gateway or 403 Forbidden page from Nginx/Cloudflare), response.json() will fail, and the code will fall back to reading response.text.

Without truncation, the entire HTML page (which can be tens of kilobytes) will be appended to the error message, logged, and potentially displayed to the user. This can flood the logs and ruin the user interface.

Truncating body_text to a reasonable length (e.g., 500 characters) prevents this issue.

Suggested change
except Exception:
body_text = str(getattr(response, "text", "") or "").strip()
if body_text:
parts.append(body_text)
except Exception:
body_text = str(getattr(response, "text", "") or "").strip()
if body_text:
if len(body_text) > 500:
body_text = body_text[:500] + "..."
parts.append(body_text)

@Anionex
Anionex force-pushed the fix/issue-420-mineru-auth-error branch from 60258f8 to e30aa8f Compare June 7, 2026 15:49
@Anionex

Anionex commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

Addressed the latest Gemini suggestions in the current head by adding failed/fail/failure detection and truncating large response.text fallbacks with regression tests. @gemini-code-assist please re-review when available.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces robust error handling and detection for MinerU authentication failures, mapping expired or invalid credentials to actionable user-facing messages rather than generic network errors. It includes helper functions for error extraction and pattern matching, integrates these checks into the file upload and polling workflows, and adds comprehensive unit and regression tests. The reviewer suggested improving the error response parser to support list-type values (such as FastAPI/Pydantic validation errors) in the detail field to prevent them from being ignored.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +85 to +93
if isinstance(body, dict):
for key in ("msg", "message", "error", "detail"):
value = body.get(key)
if isinstance(value, str) and value.strip():
parts.append(value.strip())
elif isinstance(value, dict):
nested = value.get("message") or value.get("msg") or value.get("error")
if isinstance(nested, str) and nested.strip():
parts.append(nested.strip())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

When parsing the error response body, if detail is a list (which is the standard format for FastAPI/Pydantic validation errors, e.g., {"detail": [{"msg": "..."}]}), it will be ignored because the code only checks for str and dict types. Surfacing these validation details is highly beneficial for debugging API integration issues.

Consider adding support for list-type values to extract nested error messages.

Suggested change
if isinstance(body, dict):
for key in ("msg", "message", "error", "detail"):
value = body.get(key)
if isinstance(value, str) and value.strip():
parts.append(value.strip())
elif isinstance(value, dict):
nested = value.get("message") or value.get("msg") or value.get("error")
if isinstance(nested, str) and nested.strip():
parts.append(nested.strip())
if isinstance(body, dict):
for key in ("msg", "message", "error", "detail"):
value = body.get(key)
if isinstance(value, str) and value.strip():
parts.append(value.strip())
elif isinstance(value, dict):
nested = value.get("message") or value.get("msg") or value.get("error")
if isinstance(nested, str) and nested.strip():
parts.append(nested.strip())
elif isinstance(value, list):
for item in value:
if isinstance(item, str) and item.strip():
parts.append(item.strip())
elif isinstance(item, dict):
nested = item.get("msg") or item.get("message") or item.get("error")
if isinstance(nested, str) and nested.strip():
parts.append(nested.strip())

@Anionex
Anionex force-pushed the fix/issue-420-mineru-auth-error branch from e30aa8f to d25a9b6 Compare June 7, 2026 15:55
@Anionex

Anionex commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

Addressed the latest Gemini suggestion in the current head by extracting list-style detail messages, with regression coverage. @gemini-code-assist please re-review when available.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces robust detection and handling for MinerU authentication errors (such as invalid or expired tokens) during file uploading and polling. It adds helper functions to parse error responses, raises immediate errors in the task manager when auth failures occur, and includes comprehensive unit and regression tests. The review feedback suggests enhancing robustness by wrapping fallback text extraction in a nested try-except block and checking that the response object is not null before performing keyword-based auth error detection to prevent false positives during network exceptions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread backend/services/file_parser_service.py Outdated
Comment on lines +80 to +83
except Exception:
body_text = _truncate_mineru_error_text(str(getattr(response, "text", "") or "").strip())
if body_text:
parts.append(body_text)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Accessing response.text or converting it to a string can occasionally raise exceptions (e.g., UnicodeDecodeError or custom mock errors in tests). Since this is error-handling/formatting code, it should be extremely robust and never propagate exceptions. Wrapping the fallback text extraction in a nested try-except block prevents any unexpected crashes.

    except Exception:
        try:
            body_text = _truncate_mineru_error_text(str(getattr(response, "text", "") or "").strip())
            if body_text:
                parts.append(body_text)
        except Exception:
            pass
References
  1. Use try-except blocks to handle potential errors and provide safe fallbacks.

Comment thread backend/services/file_parser_service.py Outdated
Comment on lines +405 to +408
if _looks_like_mineru_auth_error(
f"{response_text} {str(e)}",
getattr(response, "status_code", None),
):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

When a network-level exception occurs (e.g., DNS resolution failure, connection timeout, or connection refused), response will be None. In such cases, the exception message str(e) might accidentally contain keywords like "auth" (e.g., in a custom base URL hostname) and "failed", leading to a false positive where a network error is incorrectly classified as a MinerU auth/token error.

To prevent this, we should ensure that response is not None before performing keyword-based auth error detection in the exception handler.

Suggested change
if _looks_like_mineru_auth_error(
f"{response_text} {str(e)}",
getattr(response, "status_code", None),
):
if response is not None and _looks_like_mineru_auth_error(
f"{response_text} {str(e)}",
getattr(response, "status_code", None),
):

Comment thread backend/services/file_parser_service.py Outdated
Comment on lines +493 to +496
if _looks_like_mineru_auth_error(
f"{response_text} {str(e)}",
getattr(response, "status_code", None),
):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Similarly, during polling, if a network-level exception occurs and response is None, we should not classify it as a MinerU auth error based on keyword matching of the exception message. Checking response is not None ensures we only flag auth errors when we actually received an HTTP response from the server.

Suggested change
if _looks_like_mineru_auth_error(
f"{response_text} {str(e)}",
getattr(response, "status_code", None),
):
if response is not None and _looks_like_mineru_auth_error(
f"{response_text} {str(e)}",
getattr(response, "status_code", None),
):

@Anionex
Anionex force-pushed the fix/issue-420-mineru-auth-error branch from d25a9b6 to 101ee7c Compare June 7, 2026 16:02
@Anionex

Anionex commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

Addressed the latest Gemini suggestions in 101ee7c by making fallback response.text extraction non-throwing and avoiding keyword-based auth classification for pure network errors without a response. Added regressions for both cases. @gemini-code-assist please re-review when available.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces specific handling for MinerU authentication errors to provide user-friendly error messages instead of generic network errors. It adds helper functions to detect and format MinerU credential failures, integrates these checks into file uploading and polling processes, and ensures that task execution fails immediately upon detecting auth errors. Comprehensive unit and regression tests are also added. The review feedback suggests refining the substring matching in _looks_like_mineru_auth_error to use word boundaries for English terms, preventing false positives such as matching 'tokenize' as 'token'.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +111 to +121
def _looks_like_mineru_auth_error(text: str | None, status_code: int | None = None) -> bool:
if status_code in (401, 403):
return True

normalized = str(text or "").lower()
if not normalized:
return False

has_auth_term = any(term in normalized for term in _MINERU_AUTH_TERMS)
has_failure_term = any(term in normalized for term in _MINERU_AUTH_FAILURE_TERMS)
return has_auth_term and has_failure_term

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The current implementation of _looks_like_mineru_auth_error uses simple substring matching (term in normalized) for all terms. This can lead to false positives where non-auth errors are misclassified as auth errors. For example, a document processing error containing the word tokenize or failed to tokenize will match token (from _MINERU_AUTH_TERMS) and failed (from _MINERU_AUTH_FAILURE_TERMS), incorrectly triggering the MinerU token expiration error message.

To prevent this, we should use word boundary matching (\b) for English terms, while keeping substring matching for Chinese terms.

def _looks_like_mineru_auth_error(text: str | None, status_code: int | None = None) -> bool:
    if status_code in (401, 403):
        return True

    normalized = str(text or "").lower()
    if not normalized:
        return False

    def contains_term(text_str: str, terms: tuple[str, ...]) -> bool:
        for term in terms:
            if any('\\u4e00' <= char <= '\\u9fff' for char in term):
                if term in text_str:
                    return True
            else:
                if re.search(r'\\b' + re.escape(term) + r'\\b', text_str):
                    return True
        return False

    has_auth_term = contains_term(normalized, _MINERU_AUTH_TERMS)
    has_failure_term = contains_term(normalized, _MINERU_AUTH_FAILURE_TERMS)
    return has_auth_term and has_failure_term

@Anionex
Anionex force-pushed the fix/issue-420-mineru-auth-error branch from 101ee7c to ecda122 Compare June 7, 2026 16:07
@Anionex

Anionex commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

Addressed Gemini latest suggestion in boundary matching by switching auth/failure keyword checks to word-boundary matching for ASCII terms (e.g., avoid tokenizing false positives like "tokenize") and added regression tests covering auth/authentic/tokenize cases. @gemini-code-assist please re-review this commit.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces robust detection and handling for MinerU authentication errors (such as invalid or expired tokens) in the file parser service and task manager. It maps these failures to clear, actionable user-facing messages rather than generic network errors, and raises immediate errors during page processing to halt execution. Additionally, comprehensive unit and regression tests have been added to verify this behavior. No review comments were provided, and the implementation looks solid.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

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.

ppt翻新报错提示不好

1 participant