Hello Jerry, I do not think the problem was identified.
The fix looks relevant. Would you agree opening a PR with the fixes ? -- Best regards, Benoit TELLIER General manager of Linagora VIETNAM <https://linagora.vn>. Product owner for Twake-Mail <https://github.com/linagora/tmail-flutter> product. Chairman of the Apache James project <https://james.apache.org/>. Mail: [email protected] Tel: (0033) 6 77 26 04 58 (WhatsApp, Signal) On Oct 20, 2026 at 4:50 PM, Jerry Malcolm (Tech) via server-dev <[email protected]> wrote: > I'm running Apache James 3.9.0 with the Guice/PostgreSQL configuration, > using Roundcube as the webmail client and its ManageSieve plugin for > managing Sieve filters. > > I've encountered what appears to be a bug in the PostgreSQL Sieve > repository implementation. Updating an existing active Sieve script > using PUTSCRIPT causes the script to become inactive. > > I've traced the behavior through the ManageSieve protocol logs and the > compiled James 3.9.0 classes. > > SYMPTOMS > > When a user edits an existing Sieve filter in Roundcube and saves it, > the script contents are successfully updated in PostgreSQL. However, the > corresponding record in the sieve_scripts table changes: > > is_active true -> false > activation_date_time timestamp -> NULL > > Consequently, none of the user's Sieve filters execute until the script > is reactivated. > > This happens when editing an individual filter, not just when creating > or deleting scripts. > > MANAGESIEVE PROTOCOL EVIDENCE > > Roundcube's ManageSieve debug log shows the following sequence: > > C: LISTSCRIPTS > S: "roundcube" ACTIVE > S: OK > > C: PUTSCRIPT "roundcube" {1570+} > S: OK > > The script was explicitly reported as ACTIVE immediately before the update. > > James successfully accepts the replacement script and returns OK, but > afterward the PostgreSQL record indicates that the script is inactive. > > Roundcube does not issue a subsequent SETACTIVE command, which I believe > is correct behavior. According to RFC 5804, replacing an existing active > script using PUTSCRIPT should preserve its active status. > > ROOT CAUSE IDENTIFIED IN JAMES 3.9.0 > > I inspected the compiled classes using javap. > > The relevant classes are: > > james-server-data-postgres-3.9.0.jar > > org.apache.james.sieve.postgres.PostgresSieveRepository > org.apache.james.sieve.postgres.PostgresSieveScriptDAO > > In PostgresSieveRepository.putScript(), the repository constructs a new > PostgresSieveScript object. > > The bytecode contains: > > 68: iconst_0 > 69: invokevirtual #81 > // PostgresSieveScript$Builder.isActive:(Z) > > In other words, the repository explicitly sets: > > .isActive(false) > > This occurs regardless of whether the script being replaced is currently > active. > > The repository then calls: > > postgresSieveScriptDAO.upsertScript(script) > > Examining PostgresSieveScriptDAO.upsertScript() reveals that it uses a > PostgreSQL INSERT ... ON CONFLICT DO UPDATE operation. > > The conflict handler updates the following fields: > > script_size > script_content > is_active > activation_date_time > > Because the repository constructs the replacement object with > isActive(false) and no activation timestamp, the upsert overwrites the > existing activation information. > > The effective SQL behavior is equivalent to: > > INSERT INTO sieve_scripts (...) > VALUES (...) > ON CONFLICT (username, script_name) > DO UPDATE SET > script_size = EXCLUDED.script_size, > script_content = EXCLUDED.script_content, > is_active = EXCLUDED.is_active, > activation_date_time = EXCLUDED.activation_date_time; > > This explains the observed behavior. > > SUGGESTED CORRECTION > > I believe the simplest correction would be in > PostgresSieveScriptDAO.upsertScript(). > > When updating an existing script, the ON CONFLICT DO UPDATE clause > should update only: > > .set(SCRIPT_SIZE, script.getScriptSize()) > .set(SCRIPT_CONTENT, script.getScriptContent()) > > It should not overwrite: > > .set(IS_ACTIVE, script.isActive()) > .set(ACTIVATION_DATE_TIME, script.getActivationDateTime()) > > This would preserve the existing activation state when replacing a > script, while allowing newly created scripts to default to inactive. > > Activation and deactivation would continue to be handled by the existing > SETACTIVE implementation. > > QUESTIONS > > 1. Is this a known issue in James 3.9.0's PostgreSQL Sieve repository? > 2. Has it already been corrected in a subsequent James release? > 3. Does the proposed change to PostgresSieveScriptDAO.upsertScript() > look appropriate, or is there another reason the activation fields are > deliberately updated during PUTSCRIPT? > > For my installation, I'm considering a temporary PostgreSQL trigger to > preserve the activation fields during script-content updates, avoiding a > custom James build. > > I can provide additional bytecode output or ManageSieve logs if helpful. > > Thanks! > > Jerry > > --------------------------------------------------------------------- > To unsubscribe, e-mail: [email protected] > For additional commands, e-mail: [email protected]
