{"record":{"id":"b52624d2074494f8","repo":"iflytek/astron-agent","slug":"invite-no-corresponding-users-found","errorCode":"INVITE_NO_CORRESPONDING_USERS_FOUND","errorMessage":"INVITE_NO_CORRESPONDING_USERS_FOUND","messagePattern":"INVITE_NO_CORRESPONDING_USERS_FOUND","errorType":"error_code","errorClass":"BusinessException","httpStatus":null,"severity":"error","filePath":"console/backend/toolkit/src/main/java/com/iflytek/astron/console/toolkit/tool/DataPermissionCheckTool.java","lineNumber":83,"sourceCode":"    private final BizConfig bizConfig;\n    private final RepoMapper repoMapper;\n    private final SparkBotMapper sparkBotMapper;\n    private final WorkflowMapper workflowMapper;\n    private final DbInfoMapper dbInfoMapper;\n    private final DbTableMapper dbTableMapper;\n    private final UserLangChainInfoMapper userLangChainInfoDao;\n    private final BotMarketDataService botMarketDataService;\n\n    /**\n     * Get the current thread's uid, throw business exception if empty.\n     *\n     * @return the current user ID\n     * @throws BusinessException if no user ID found in thread local\n     */\n    public String getThreadLocalUidNoNull() {\n        String uid = UserInfoManagerHandler.getUserId();\n        if (uid == null) {\n            throw new BusinessException(ResponseEnum.INVITE_NO_CORRESPONDING_USERS_FOUND);\n        }\n        return uid;\n    }\n\n    /**\n     * Check if currently in space context.\n     *\n     * @return true if in space context, false otherwise\n     */\n    private boolean inSpace() {\n        return SpaceInfoUtil.getSpaceId() != null;\n    }\n\n    /**\n     * Get the current SpaceId (may be null).\n     *\n     * @return current space ID or null\n     */","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/console/backend/toolkit/src/main/java/com/iflytek/astron/console/toolkit/tool/DataPermissionCheckTool.java#L65-L101","documentation":"DataPermissionCheckTool.getThreadLocalUidNoNull() reads the current user id from UserInfoManagerHandler (a ThreadLocal populated by the auth filter/interceptor) and throws BusinessException(INVITE_NO_CORRESPONDING_USERS_FOUND) when it is null. It is the mandatory uid source for every ownership check in this tool (uid, checkBotBelong, checkEvalSceneBelong, checkDbUpdateBelong, noPermission, etc.), so any request that reaches permission checks without an authenticated user context fails with this error. Despite the enum's invite-themed name, here it really means 'no user identity in ThreadLocal'.","triggerScenarios":"Calling any DataPermissionCheckTool check outside of an authenticated HTTP request thread: async/@Async tasks, Kafka consumers, scheduled jobs, internal service-to-service calls that bypass the login interceptor, WebSocket/daemon threads, or tests that don't set UserInfoManagerHandler; also requests where the auth filter skipped setting the uid (unauthenticated/anonymous or expired session not rejected earlier).","commonSituations":"Moving a service method from a controller to an async executor or @Scheduled task and suddenly hitting this error; calling console services via internal RPC where the auth header isn't propagated; writing unit/integration tests for bot/repo/tool permission checks without stubbing the ThreadLocal.","solutions":["Ensure the calling path is an authenticated request that passes the auth interceptor so UserInfoManagerHandler receives the uid; check the request carries valid login credentials/token.","If running in async/scheduled/consumer code, explicitly set the ThreadLocal (UserInfoManagerHandler) at task start with the propagated uid and clear it in a finally block.","Propagate user identity across threads: capture uid before submitting the async task and restore it inside the task, or use a TaskDecorator.","In tests, set the ThreadLocal uid in setup (@BeforeEach) and remove it in teardown before invoking permission checks."],"exampleFix":"// before\nexecutor.submit(() -> dataPermissionCheckTool.checkBotBelong(bot)); // uid ThreadLocal absent in worker thread\n// after\nString uid = UserInfoManagerHandler.getUserId();\nexecutor.submit(() -> {\n    UserInfoManagerHandler.setUserId(uid);\n    try {\n        dataPermissionCheckTool.checkBotBelong(bot);\n    } finally {\n        UserInfoManagerHandler.clear();\n    }\n});","handlingStrategy":"type-guard","validationCode":"String uid = UserInfoManagerHandler.getUserId();\nif (uid == null) {\n    throw new IllegalStateException(\"no user context: ensure request passes auth interceptor or set ThreadLocal in async task\");\n}","typeGuard":"public static boolean hasUserContext() {\n    return UserInfoManagerHandler.getUserId() != null;\n}","tryCatchPattern":"try {\n    dataPermissionCheckTool.checkBotBelong(bot);\n} catch (BusinessException e) {\n    if (ResponseEnum.INVITE_NO_CORRESPONDING_USERS_FOUND.equals(e.getEnum())) {\n        throw new BusinessException(ResponseEnum.INVITE_NO_CORRESPONDING_USERS_FOUND, \"user session missing — re-authenticate\");\n    }\n    throw e;\n}","preventionTips":["Never call DataPermissionCheckTool methods from @Async/@Scheduled/consumer threads without first setting the uid ThreadLocal.","Wrap ThreadLocal set/clear in try/finally or a TaskDecorator when propagating identity across executors.","In tests, seed UserInfoManagerHandler in @BeforeEach and clear it in @AfterEach.","Authenticate and reject anonymous requests at the interceptor level before permission checks run."],"tags":["threadlocal","authentication","user-context","async","permission"],"backgroundTag":"authentication-required","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}