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]

Reply via email to