{"record":{"id":"084fb68de3b7ed06","repo":"walkor/workerman","slug":"request-session-fail-header-already-send","errorCode":null,"errorMessage":"Request->session() fail, header already send","messagePattern":"Request->session\\(\\) fail, header already send","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/Protocols/Http/Request.php","lineNumber":385,"sourceCode":"     * @return string\n     * @throws Exception\n     */\n    public function sessionId(?string $sessionId = null): string\n    {\n        if ($sessionId) {\n            unset($this->context['sid'], $this->context['session']);\n        }\n        if (!isset($this->context['sid'])) {\n            $sessionName = Session::$name;\n            $sid = $sessionId ? '' : $this->cookie($sessionName);\n            // Strip surrounding double quotes (RFC 6265 allows DQUOTE-wrapped cookie values).\n            if (is_string($sid) && isset($sid[1]) && $sid[0] === '\"' && $sid[-1] === '\"') {\n                $sid = substr($sid, 1, -1);\n            }\n            $sid = $this->isValidSessionId($sid) ? $sid : '';\n            if ($sid === '') {\n                if (!$this->connection) {\n                    throw new RuntimeException('Request->session() fail, header already send');\n                }\n                $sid = $sessionId ?: static::createSessionId();\n                $cookieParams = Session::getCookieParams();\n                $this->setSidCookie($sessionName, $sid, $cookieParams);\n            }\n            $this->context['sid'] = $sid;\n        }\n        return $this->context['sid'];\n    }\n\n    /**\n     * Check if session id is valid.\n     *\n     * @param mixed $sessionId\n     * @return bool\n     */\n    public function isValidSessionId(mixed $sessionId): bool\n    {","sourceCodeStart":367,"sourceCodeEnd":403,"githubUrl":"https://github.com/walkor/workerman/blob/1391112a61d23020e11e7b89f17050f6cfaea431/src/Protocols/Http/Request.php#L367-L403","documentation":"sessionId() must mint a new session id and emit a Set-Cookie header when the request carries no valid session cookie. That is only possible while the Request still owns its connection ($request->connection); once the response/headers have already been sent or the request was detached, no header can be added, so Workerman throws instead of silently creating an unusable session.","triggerScenarios":"Calling $request->session() (or sessionId(null)) after already sending the response on the connection; using the Request object inside a deferred context (Timer callback, queue consumer, coroutine resumed later) where connection is null; storing the request and touching session after onMessage returned.","commonSituations":"Code that pushes an early response (e.g. $connection->send('ok')) then continues processing and reads the session; background jobs that receive the whole Request object; middleware that emits headers before session start; workerman v5 coroutine code awaiting something before touching session.","solutions":["Open the session before any output: call $request->session() at the top of onMessage, before sending anything on the connection","Do not use the Request in deferred/background tasks; extract the session id ($request->sessionId()) first and pass that string to the job","Ensure the client actually receives the PHPSSESSID cookie so later requests have a valid sid and never need the header-write path"],"exampleFix":"// before\n$connection->send($response);\n$session = $request->session(); // throws 'header already send'\n\n// after\n$session = $request->session();\n$session->set('uid', 1);\n$connection->send($response);","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function canCreateSession(Workerman\\Protocols\\Http\\Request $request): bool\n{\n    // A new Set-Cookie can only be emitted while the request still owns its connection.\n    return $request->connection !== null;\n}","tryCatchPattern":"try {\n    $session = $request->session();\n} catch (RuntimeException $e) {\n    if (str_contains($e->getMessage(), 'header already send')) {\n        // headers already sent: log and degrade (no session this request)\n        return $response->withStatus(500);\n    }\n    throw $e;\n}","preventionTips":["First statement of onMessage: open the session; last: send the response","Never pass Request objects into Timer callbacks or queues; extract $request->sessionId() and pass the string","In coroutines, capture the session id before the first await and re-attach by id afterwards"],"tags":["php","workerman","http","session","request-lifecycle"],"backgroundTag":"headers-already-sent","analyzedSha":"1391112a61d23020e11e7b89f17050f6cfaea431","analyzedAt":"2026-08-21T02:05:46.744Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}