Bugfix Issue 616 SessionDoubleStart RemotePlanLinks - sebiboga/testlink-upgraded GitHub Wiki
Issue 616 — session_start(): Ignoring session_start() because a session is already active on every remote/plan-link generation
Issue: #616
Branch: fix/issue-616
Fix commit: 79420c632
Status: FIXED & VERIFIED (2026-08-28)
Symptom
Every document generation through the legacy direct-link flow (lnl.php?apikey=...&type=testreport_onbuild&build_id=N → redirect → lib/results/printDocument.php) logs into the Event Viewer / events table:
E_NOTICE (PHP 8.3) / E_WARNING (PHP 8.5)
session_start(): Ignoring session_start() because a session is already active
(started from lib/functions/common.php on line 315) - in common.php - Line 315
First observed 2026-08-23 (first-ever real use of per-build plan links). Functionality unaffected — it is log noise, but it writes a PHP error row into the DB on every generation.
Root cause chain
lib/functions/common.phptop-level includescsrf.php(if( !defined('TL_APICALL') ) { require_once("csrf.php"); }).lib/functions/csrf.php:211runsdoSessionStart(false);at include time → firstsession_start()atlib/functions/common.php:315→ the session becomesPHP_SESSION_ACTIVEon every request.- The remote/plan-link controllers pass
clearSession=true:lnl.php:205$opt = ['setPaths' => true, 'clearSession' => true]→setUpEnvForRemoteAccess(lnl.php:230) orsetUpEnvForAnonymousAccess(lnl.php:264);ltcp.php:53uses the same$opt.
setUpEnvForRemoteAccess/setUpEnvForAnonymousAccess(lib/functions/common.php:1161,:1310) runif($my['opt']['clearSession']) { $_SESSION = null; }. Measured behaviour: the superglobal is unbound butsession_status()remainsPHP_SESSION_ACTIVE.- Both then call
doSessionStart($setPaths)(common.php:1166,:1315). The old guard at:314wasif(!isset($_SESSION))— on a nulled superglobal that evaluates true, sosession_start()is invoked a second time while the session is already active → PHP "Ignoring session_start() because a session is already active (started from common.php on line 315)". watchPHPErrors(lib/functions/logger.class.php:1483,set_error_handler("watchPHPErrors")) funnels it vialogWarningEventinto theeventstable.
The trigger is inside the
lnl.phprequest (pre-redirect), not insideprintDocument.php—printDocument.phpnever passesclearSession=true, so it never nulls the session. PHP docssession_status()explicitly warns thatisset($_SESSION)/session_id()are unreliable once the superglobal is unbound.
The fix
lib/functions/common.php — doSessionStart() guard changed from:
if(!isset($_SESSION)) {
session_start();
}
to:
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
session_status() is the only robust "not running → start" check (per PHP manual and php-src#10736 guidance). Analyze by state:
- No active session (
PHP_SESSION_NONE): start — unchanged. - Active session + nulled
$_SESSION(the clearSession case): skip the second start — the E_NOTICE/E_WARNING disappears; subsequent$_SESSION[...]writes rebuild the array and PHP persists it at request end, sosetPaths()/basehref behaviour is identical. - Active session + intact
$_SESSION: skip — identical to the old guard's happy path.
lib/functions/configCheck.php:752 and login.php:265 contain the same micro-guard pattern but in flows where $_SESSION is never nulled beforehand — left untouched (minimal-fix scope).
Verification
Fixture: php tmp/fixtures_616.php (tproject=1, tplan=6, build=1, plan api_key + user script_key). All checks against a fresh events watermark:
| # | Case | Result |
|---|---|---|
| 1 | curl lnl.php?apikey=<64h plan key>&type=testreport_onbuild&build_id=1 → 302, 0 new events |
PASS |
| 2 | curl lnl.php?apikey=<32h user key>&type=testreport_onbuild&build_id=1 (remote-access path) → 302, 0 new events |
PASS |
| 3 | Browser (logged-in) full redirect chain → document generated ("Test Plan Execution Report (on specific build)", UPD616/Plan616/Build616), 0 new ERROR/WARNING events | PASS |
| 4 | GUI login + navigation regression — navBar/aside/mainframe all load | PASS |
| 5 | Event Viewer sweep — no log_level=1/2 rows created by these flows | PASS |
Screenshot of the generated report (proves the plan-link flow is fully operational after the fix):

Files changed
lib/functions/common.php— one-line session-start guard indoSessionStart()+ explanatory comment.tmp/TLU_Test_Cases.md— regression suiteRegression — Issue #616(5 cases, all PASS; local file,tmp/is gitignored).