{"record":{"id":"eb02c150a6330015","repo":"infiniflow/ragflow","slug":"main-must-be-defined-or-exported","errorCode":null,"errorMessage":"main() must be defined or exported.","messagePattern":"main\\(\\) must be defined or exported\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"agent/sandbox/result_protocol.py","lineNumber":46,"sourceCode":"if __name__ == \"__main__\":\n    import base64\n    import json\n\n    result = main(**{args_json})\n    payload = json.dumps({{\"present\": True, \"value\": result, \"type\": \"json\"}}, ensure_ascii=False, separators=(\",\", \":\"))\n    print(\"{RESULT_MARKER_PREFIX}\" + base64.b64encode(payload.encode(\"utf-8\")).decode(\"ascii\"))\n'''\n\n\ndef build_javascript_wrapper(code: str, args_json: str) -> str:\n    return f\"\"\"{code}\n\nconst __ragflowArgs = {args_json};\n\n(async () => {{\n  const __ragflowMain = typeof main !== 'undefined' ? main : module.exports && module.exports.main;\n  if (typeof __ragflowMain !== 'function') {{\n    throw new Error('main() must be defined or exported.');\n  }}\n  const output = await Promise.resolve(__ragflowMain(__ragflowArgs));\n  if (typeof output === 'undefined') {{\n    throw new Error('main() must return a value. Use null for an empty result.');\n  }}\n  const payload = JSON.stringify({{ present: true, value: output, type: 'json' }});\n  if (typeof payload === 'undefined') {{\n    throw new Error('main() returned a non-JSON-serializable value.');\n  }}\n  console.log('{RESULT_MARKER_PREFIX}' + Buffer.from(payload, 'utf8').toString('base64'));\n}})();\n\"\"\"\n\n\ndef extract_structured_result(stdout: str) -> tuple[str, dict[str, Any]]:\n    if not stdout:\n        return \"\", {}\n","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/agent/sandbox/result_protocol.py#L28-L64","documentation":"Thrown from validate_connector_settings when python-gitlab's auth() or projects.get() raises GitlabAuthenticationError. The GitLab Personal Access Token (PAT) is invalid, revoked, or the account it belonged to no longer authenticates. It maps to CredentialExpiredError so the backend can prompt for credential refresh.","triggerScenarios":"Calling validate_connector_settings() (typically from the connector-credential update UI or a test-connection endpoint) when the stored PAT is expired/revoked, was pasted with whitespace or a typo, or belongs to a deleted/deactivated user. Also occurs with OAuth tokens whose refresh failed.","commonSituations":"Free-tier PAT expiry (90-day for gitlab.com), admin rotating tokens without updating the connector credential, tokens copied from a password manager with trailing newlines, SSO-enforced accounts with personal tokens disabled.","solutions":["Verify the PAT still works: curl -H 'PRIVATE-TOKEN: <token>' https://gitlab.example.com/api/v4/user — 401 means regenerate the token","Regenerate the PAT in GitLab (User Settings > Access Tokens) with api/read_api + read_repository scopes and re-enter it in the connector credential","If OAuth-based, disconnect and redo the OAuth flow to mint a fresh refresh token","Confirm the token owner account is active and not blocked/ deactivated"],"exampleFix":"# before: token stored with whitespace / expired\ncredentials = {\"gitlab_access_token\": \"  glpat-XXXX  \\n\"}\n\n# after: trim and validate before saving\nimport re\ntoken = credentials[\"gitlab_access_token\"].strip()\nif not re.fullmatch(r\"glpat-[A-Za-z0-9_-]{20,}\", token):\n    raise ValueError(\"Malformed GitLab PAT — expected glpat-...\")\ncredentials[\"gitlab_access_token\"] = token","handlingStrategy":"try-catch","validationCode":"import requests\n\ndef gitlab_token_alive(base_url: str, token: str) -> bool:\n    r = requests.get(\n        f\"{base_url.rstrip('/')}/api/v4/user\",\n        headers={\"PRIVATE-TOKEN\": token},\n        timeout=10,\n    )\n    return r.status_code == 200","typeGuard":"def is_valid_gitlab_credential_payload(creds: dict) -> bool:\n    tok = creds.get(\"gitlab_access_token\", \"\")\n    return bool(tok) and tok == tok.strip() and tok.startswith(\"glpat-\")","tryCatchPattern":"from common.data_source.exceptions import CredentialExpiredError\n\ntry:\n    connector.validate_connector_settings()\nexcept CredentialExpiredError:\n    # prompt user to re-enter token, then retry once\n    connector.load_credentials(prompt_for_new_token())\n    connector.validate_connector_settings()","preventionTips":["Set a calendar for PAT expiry (gitlab.com free tokens expire in 90 days) and rotate proactively","Trim and format-check tokens before persisting them","Run validate_connector_settings() right after saving credentials so expiry surfaces at config time, not mid-index"],"tags":["gitlab","authentication","credentials","connector","validation"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}