From 03d79523da83c5d435b66bed3fb8326a6f50c84d Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:04:37 +0200 Subject: [PATCH] fix: load app config on remaining retention-relevant routes The messages routes (branch, artifact, post), share-link create/patch, and the agent chat-abort route read req.config.interfaceConfig but never ran configMiddleware, so req.config was undefined and retention was skipped. Under ephemeral (and all) this let branched/edited/aborted messages and shared links persist without isTemporary/expiredAt, outliving the chat. Apply configMiddleware to those routes so retention is enforced consistently. Add a getSharedLinkExpiration ephemeral test and keep route test middleware mocks in sync. --- api/server/routes/messages.js | 6 +++--- packages/api/src/files/retention.spec.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/api/server/routes/messages.js b/api/server/routes/messages.js index 1f6188b379..37b73fbadf 100644 --- a/api/server/routes/messages.js +++ b/api/server/routes/messages.js @@ -109,7 +109,7 @@ router.get('/', async (req, res) => { * @param {string} req.body.agentId - The agentId to filter content by * @returns {TMessage} The newly created branch message */ -router.post('/branch', async (req, res) => { +router.post('/branch', configMiddleware, async (req, res) => { try { const { messageId, agentId } = req.body; const userId = req.user.id; @@ -190,7 +190,7 @@ router.post('/branch', async (req, res) => { } }); -router.post('/artifact/:messageId', async (req, res) => { +router.post('/artifact/:messageId', configMiddleware, async (req, res) => { try { const { messageId } = req.params; const { index, original, updated } = req.body; @@ -283,7 +283,7 @@ router.get('/:conversationId', validateMessageReq, async (req, res) => { } }); -router.post('/:conversationId', validateMessageReq, async (req, res) => { +router.post('/:conversationId', validateMessageReq, configMiddleware, async (req, res) => { try { const message = { ...req.body, conversationId: req.params.conversationId }; const reqCtx = { diff --git a/packages/api/src/files/retention.spec.ts b/packages/api/src/files/retention.spec.ts index bfd327508b..1fb8bc213e 100644 --- a/packages/api/src/files/retention.spec.ts +++ b/packages/api/src/files/retention.spec.ts @@ -394,6 +394,22 @@ describe('retention helpers', () => { ).resolves.toBe(expirationDate); }); + it('returns a fresh expiry for retentionMode EPHEMERAL conversations without an expiration', async () => { + dependencies.getConvo.mockResolvedValue({ expiredAt: null }); + + await expect( + getSharedLinkExpiration( + { + req: request({ + config: { interfaceConfig: { retentionMode: RetentionMode.EPHEMERAL } }, + }), + conversationId: 'convo-1', + }, + dependencies, + ), + ).resolves.toBe(expirationDate); + }); + it('returns an expired source conversation date so callers can reject the share', async () => { const expiredAt = new Date(Date.now() - 60 * 60 * 1000); dependencies.getConvo.mockResolvedValue({ expiredAt });