My apologies. I made a mistake in the minimal examples to reproduce: 1. session_id($id); session_start(); echo session_id(); // empty!
2. session_id($id); echo "b"; session_start(); echo session_id(); // FINE!! 3. echo "a"; session_id($id); echo "b"; session_start(); echo session_id(); // empty again! Sincerely, Greg Magarshak <http://qbix.com/invest> On Wed, Dec 17, 2025 at 12:06 PM Gregory Magarshak <[email protected]> wrote: > I am using my PHP framework inside my PHP scripts, which has code designed > to work across multiple environments, including web requests and CLI > scripts, and uses handlers which extends SessionHandler. I also > tried implements SessionUpdateTimestampHandlerInterface and validateId. > Unfortunately, the session handling in PHP has a trap door internally, that > was likely motivated by not outputting headers after starting to send HTTP > response body. > > It's mystical. > 1. session_id($id); session_start(); echo session_id(); // empty! > 2. session_id($id); echo "Session started.\n"; session_start(); echo > session_id(); // FINE!! > 3. session_id($id); echo "Session started.\n"; session_start(); echo > session_id(); // empty again! > > I can't believe it is there in 2025. > > *I tried this with models trained on PHP documentation, and asked it to > provide a link explaining this behavior. This is what it said:* > > I’m going to be very careful here and *not hallucinate citations*, > because after checking the PHP manual, RFCs, and known bug threads, there > is *no official documentation that explains this exact behavior in CLI*. > There is also *no single bug report* that spells it out end-to-end the > way you’ve reverse-engineered it. > > So here is the honest answer, split into *what is documented*, *what is > observable in source*, and *what is not fixable from userland*. > ------------------------------ > Short answer (truthful) > > There is *no supported way to “fix” this in PHP userland*. > > What you are seeing is a consequence of *how ext/session couples > session-ID finalization to header emission state*, and that logic *cannot > be made reliable in CLI*, because: > > - > > CLI has *no real headers* > - > > session_id() *resets the session module’s internal header bookkeeping* > - > > Output is tracked via a *single edge-triggered latch* > - > > That latch is *not re-entrant* and *not monotonic* > - > > Earlier output poisons later output > > This is *by design*, not a bug PHP intends to fix. > ------------------------------ > Let’s explain your three cases precisely Case 1 — fails > > self::id($id);session_start();echo session_id(); // empty > > *Why:* > > - > > session_id($id) resets session state > - > > No output has occurred since that reset > - > > PHP believes headers are still possible > - > > In CLI, that means “wait forever” > - > > Session ID never commits > > ------------------------------ > Case 2 — works > > self::id($id);echo "Session started.\n";session_start();echo session_id(); // > OK > > *Why:* > > - > > session_id($id) resets session state > - > > Output occurs *after the reset* > - > > Header latch flips to “headers impossible” > - > > session_start() commits the ID > > ------------------------------ > Case 3 — fails again > > echo 'a';self::id($id);echo "Session started.\n";session_start();echo > session_id(); // empty > > *Why this feels impossible (but isn’t):* > > - > > echo 'a' flips the latch > - > > session_id($id) *clears the latch* > - > > *Critically*: the latch is *edge-triggered*, not level-triggered > - > > The second echo *does not re-arm it* > - > > session_start() still thinks headers are possible > > This is the part that feels mystical, but it’s consistent. > ------------------------------ > The deep reason (this is the core insight) The session module tracks *“output > since last reset”*, not “output happened” > > Internally, the logic is effectively: > > if (output_has_occurred_since_last_session_reset) { > commit_session_id(); > } else { > defer(); > } > > And: > > - > > session_id() *is a session reset* > - > > Output before that reset is discarded > - > > Output after that reset must be the *first edge* > - > > Further output does not help > > This logic exists because, in HTTP: > > - > > Changing the session ID means cookies may change > - > > Cookies are headers > - > > Header logic must be reset when the ID changes > > That logic *leaks into CLI unchanged*. > > Sincerely, > Greg Magarshak > > <https://76183cf5.streak-link.com/CsqFLR9dXbkQa2qdJQNqfbB-/http%3A%2F%2Fqbix.com%2Finvest> > > ᐧ > ᐧ >
