{"record":{"id":"28d19a54f5dfa750","repo":"w7corp/easywechat","slug":"no-suite-ticket-found","errorCode":null,"errorMessage":"No suite_ticket found.","messagePattern":"No suite_ticket found\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/OpenWork/SuiteTicket.php","lineNumber":55,"sourceCode":"        return $this;\n    }\n\n    public function setTicket(string $ticket): static\n    {\n        $this->cache->set($this->getKey(), $ticket, 6000);\n\n        return $this;\n    }\n\n    /**\n     * @throws RuntimeException\n     */\n    public function getTicket(): string\n    {\n        $ticket = $this->cache->get($this->getKey());\n\n        if (! $ticket || ! is_string($ticket)) {\n            throw new RuntimeException('No suite_ticket found.');\n        }\n\n        return $ticket;\n    }\n}\n","sourceCodeStart":37,"sourceCodeEnd":61,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/OpenWork/SuiteTicket.php#L37-L61","documentation":"Thrown by SuiteTicket::getTicket() when the cache lookup for the stored suite_ticket returns nothing or a non-string. WeChat pushes a fresh suite_ticket to your app every ~10 minutes via the OpenWork callback; this error means no ticket has ever been stored (or it was evicted) in the cache the library reads.","triggerScenarios":"Any code path that resolves suite_access_token (SuiteAccessToken::refresh() pulls suiteTicket?->getTicket()) before a ticket was ever cached: fresh deployments, cache flush/restart with volatile cache (array/file), callback endpoint never invoked, or callback handled by a different process with a different cache store.","commonSituations":"New environment where the ticket push callback is not yet wired; local development where the push URL points at production but local code asks for a ticket; using Redis cache in callback but array cache in worker; ticket key evicted by short TTL right before get_suite_token runs.","solutions":["Wire the OpenWork message callback and persist the ticket in handle('suite_ticket', ...) so pushes land in the same cache the app reads","Deploy/hit the callback once and wait up to 10 minutes for the next push, then retry","Point both the callback handler and API code at the same persistent cache (Redis) and verify the cache key with getKey()","As a stopgap for testing, seed the ticket manually via SuiteTicket::setTicket()"],"exampleFix":"// before\n$openWork->suite_access_token->getToken(); // RuntimeException: No suite_ticket found.\n// after - make sure callback saves it and share the cache\n$openWork->server->handle('suite_ticket', fn ($message, \\EasyWeChat\\OpenWork\\SuiteTicket $ticket) => $ticket->setTicket($message->SuiteTicket));\n// ... after next push (<=10 min):\n$openWork->suite_access_token->getToken();","handlingStrategy":"retry","validationCode":"$hasTicket = (static function () use ($suiteTicket) {\n    try { $suiteTicket->getTicket(); return true; }\n    catch (\\EasyWeChat\\Kernel\\Exceptions\\RuntimeException) { return false; }\n})();\nif (! $hasTicket) { /* defer job ~10 min or fail gracefully */ }","typeGuard":null,"tryCatchPattern":"use EasyWeChat\\Kernel\\Exceptions\\RuntimeException;\ntry {\n    $ticket = $suiteTicket->getTicket();\n} catch (RuntimeException $e) {\n    if ($e->getMessage() === 'No suite_ticket found.') {\n        // ticket push pending: reschedule, don't crash the job\n        $this->release(600);\n        return;\n    }\n    throw $e;\n}","preventionTips":["Wire the suite_ticket callback handler from day one and verify it returns 'success'","Use a persistent shared cache so tickets survive restarts","Queue consumers: reschedule on cache miss instead of failing"],"tags":["wechat-work","open-work","suite-ticket","cache-miss","callback"],"backgroundTag":"callback-ticket-missing","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}