Skip to content

Issue 107 Signed Out While Still Working

Ed Mozley edited this page Aug 28, 2026 · 1 revision

Signed out in the middle of working (issue #107)

Editing a task and saving it dropped you back at the login screen. Tickets and assets were fine; only tasks did it. It could not be reproduced by two other people running the same version.

Reported in issue #107 by tjedelhauser, who narrowed it to tasks and established that an older instance of theirs did not do it. mbsouth did the equally valuable work of showing it did not happen on a native Debian install, which is what made it clear the difference was environmental rather than in the tasks code.

Fixed in fd5d98b4, released as update #1260.

FreeITSM was letting PHP delete the sessions of people who were actively using it. Nothing to do with tasks, and nothing to do with Docker, though it presents as both.


1. The short version, in plain English

Your session lives in a small file on the server. PHP tidies away sessions that have been abandoned, and to decide whether a session is abandoned it asks one question:

When was this file last written to?

Not when was it last read. Not is that person still clicking things. Just: when did it last change.

FreeITSM reads your session on every single request β€” that is how it knows who you are β€” but it only changes it when something about you changes, which is almost never. So while you sat there working, your session file was being read hundreds of times and written zero times.

After 24 minutes of that, PHP looked at the file, saw nothing had changed since you signed in, concluded you had gone home, and deleted it. You were mid-sentence.

Constant use looked exactly like total inactivity.

The one thing that saved you was loading a full page, which does update the file. That is the entire reason this looked like a bug in Tasks.


2. What you actually saw

It does not announce itself. It goes like this:

  1. You are working on the Tasks board, editing a task in the panel. Everything is normal.
  2. At some point the page quietly stops responding. You click a task and it does not open. You change a field and nothing happens. No error, no message, nothing in the way.
  3. You assume the page has got itself stuck, so you refresh.
  4. You are on the login screen.

Step 2 is the part that makes it confusing. The session is already gone by then, so every background request is being refused β€” but the Tasks screen does not say so, it simply does nothing. The logout only becomes visible when you reload, which makes it feel as though refreshing caused it.


3. Why tasks, and not tickets or assets

Because the Tasks board is a single page.

You open it once and then work inside it: the detail panel, the fields, the subtasks are all updated in the background without the page ever reloading. You can easily spend half an hour there. During all of that, nothing writes to your session file, so the clock never restarts.

Ticket work is different. You move between the inbox, a ticket, a search, settings β€” and every one of those page loads updates the session file, resetting the 24 minutes. You would have to work unusually still for a ticket session to age out.

So the tasks code was never at fault. Tasks was simply the one place you could sit still for long enough.


4. Why more people did not notice

This is the interesting part, and worth writing down.

Because it is indistinguishable from an ordinary session timeout

Being logged out of a web application is not, on the face of it, a bug. It is a thing that happens. You shrug, sign back in, and carry on with your day.

Almost nobody reports "I got logged out". It looks like the software working as intended β€” arguably like the software being careful. The people this was happening to were very likely writing it off as a security timeout, which is exactly what it looked like. It had been in the product since 25 May.

Because you cannot give steps to reproduce it

Even somebody who suspected a fault would struggle to report it. It needs 24 minutes of a particular kind of work, and then a one-in-a-hundred roll of the dice. Sit down to reproduce it deliberately and you will fail, because reproducing things involves clicking around β€” which reloads pages, which resets the clock. Trying to catch it is what prevents it.

The reporter succeeded only because they hit it repeatedly during real work, with the one workflow that keeps you on a single page.

Because the odds vary enormously by platform

PHP's tidying is a dice roll on each request, and how loaded the dice are depends entirely on how PHP was installed. This is measured, not assumed:

Install Setting How often the collector runs
Official php:*-apache Docker image gc_probability 1, gc_divisor 100 1 request in 100 β€” fires during an ordinary afternoon
Typical WAMP (php.ini-development) gc_probability 1, gc_divisor 1000 1 in 1000 β€” ten times rarer
Debian / Ubuntu packages gc_probability 0 Never in-process; a cron job sweeps every 30 minutes

So the reporter, on Docker, met it regularly. On a development machine you would be unlucky to see it, and a developer reloading pages constantly resets the clock long before it matters.

Debian is not immune either, which is worth stating clearly because it is tempting to conclude otherwise. Its sessionclean cron deletes with find -cmin, and a file's change time is no more refreshed by reading it than its modification time is. The blind spot is identical β€” it simply gets one chance every half hour instead of one per hundred requests. That reads as immunity in a short test without being immunity.

The trigger needs both halves: 24 minutes with no page load, and the collector happening to run. Change either and the bug disappears. That is why two people testing the same version in good faith could not reproduce it, and it is a much better explanation than "their platform is unaffected".


5. What was actually wrong

PHP's file session handler deletes session files older than session.gc_maxlifetime β€” 1440 seconds, 24 minutes β€” judged by the file's timestamp.

That is normally kept honest for you. session.lazy_write is on by default, and it does something subtle and helpful: when a session closes and the data has not changed, PHP skips the pointless rewrite but still touches the file. That single touch is what stops an active session ever looking idle.

FreeITSM's data endpoints never reach that close:

session_start(['read_and_close' => true]);

They read the session and shut it immediately. That is deliberate and correct β€” it releases the session lock so one slow request cannot block every other request from the same browser, which is why it was applied across the codebase in #389 (25 May 2026). But closing that way skips the timestamp update entirely.

717 endpoints open the session like this. Every one of them kept you signed in in every sense except the one being measured.

That date is also the answer to the reporter's own observation that an older instance of theirs was unaffected: it predates #389.


6. The fix

includes/session_keepalive.php restores the timestamp update that read_and_close skips, and does nothing else. One stat and one touch: no session lock, no re-open, so the reason read_and_close exists is untouched.

It is included from functions.php alongside the other session concerns that run on include, because all 717 endpoints already include that file β€” one require rather than 717.

Sessions that are genuinely idle still expire on exactly the old schedule. The refresh only happens while a request is actually being served, which is the correct definition of "still in use" and the one PHP intended all along. This is a fix for miscounting, not a way of keeping sessions alive longer.


7. πŸ“ The files involved

File What changed
includes/session_keepalive.php New. Refreshes the session file's timestamp when the session was opened with read_and_close.
includes/functions.php Includes it, next to session_security.php, request_guard.php and password_gate.php.

πŸ—ƒ Already correct

session_start(['read_and_close' => true]) Stays. Releasing the session lock is right; it just needed the timestamp kept up.
The tasks module Never at fault. It is where the bug was visible, not where it lived.
The service layer Also never at fault, though a reasonable first suspicion given tasks had been refactored onto it.
Docker's php.ini Its settings are PHP's own defaults. Turning the collector off to hide this would leave session files accumulating forever.

8. How it was verified

Reproduced first, in a throwaway Docker install built from the current tree β€” because two people had already failed to reproduce it, and a fix for a bug you have not seen is a guess.

The mechanism was then established by measurement rather than argument:

Test Result
Session file timestamp after an API call Unchanged
Same after a full page load Updated
Same API call with the pre-#389 session_start() shape Updated β€” pinning the regression

Then the bug itself, end to end: age the session file 25 minutes, make 300 ordinary API calls β€” the file is deleted, the board redirects to the login screen, and saving a task returns "Not authenticated". With the fix, the same 300 calls leave the session refreshed and working.

The negative control is the one that mattered. A fix that simply stopped sessions expiring would pass every test above and be a security regression. So: a genuinely idle session, aged identically, while a different user works β€” it is still collected. Expiry is intact; it now measures activity instead of luck.

Two mistakes are worth recording, because both produced confident wrong answers:

  • An apparent spontaneous logout during the investigation was the test tool's own cookie jar dropping a session cookie, not the bug. Checked before it was believed.
  • The first attempt at the pre-#389 comparison gave a false negative, because opcache was serving the cached version of the file that had just been edited. The real answer only appeared after a reload. In a container, an edited PHP file is not necessarily the file being executed.

9. What this means for you

Update, and it stops. Nothing to configure, and no session settings to change.

If people on your installation have been quietly grumbling about being signed out during the day β€” particularly anyone who lives on the Tasks board β€” this was very likely why, and they probably never mentioned it because being logged out does not feel like a fault worth reporting.

You are still signed out when you are genuinely idle, on exactly the same schedule as before. That has not been relaxed.


Related pages

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally