Session: glimmering-imagining-sparrow-subagent-aa70ba5 — 2026-01-27

doctolib-doctolib · Claude · branch master · claude-haiku-4-5-20251001 · 2026-01-27 13:43 · 1 msgs · 29 tools · 13 min read · updated 3 months ago

I need to understand a document import issue where:

tools: Bash, Grep, Glob, Read
← doctolib-doctolib Download .md .txt .json
Tool calls
Read Read: 13 calls (44.8%) 13 Grep Grep: 10 calls (34.5%) 10 Glob Glob: 4 calls (13.8%) 4 Bash Bash: 2 calls (6.9%) 2
Token usage4.7M total
Input19.5K
Cache creation334.4K
Cache read4.4M
Output73
Cache hit ratio93%· healthy

Project: doctolib-doctolib · Branch: master · Mode: ` · **Model:**claude-haiku-4-5-20251001`

Stats: 1 user messages, 29 tool calls, tools used: Bash, Grep, Glob, Read.

Conversation

Turn 1 — User

I need to understand a document import issue where: 1. When UPLOAD_DOCUMENT_WRITE feature switch is on, documents are imported via /v2 endpoints (PUT/POST) 2. After successful import, viewing the document immediately fails 3. After waiting ~5 minutes or logout/login, viewing works

Please explore: 1. Find where UPLOAD_DOCUMENT_WRITE feature switch is used in the document upload flow 2. Look for /v2 endpoints for document upload (PUT/POST operations) 3. Find how documents are uploaded/created when this FS is enabled 4. Look for React Query cache invalidation after document uploads (specifically related to the decryptedDocuments query) 5. Check if there's a queryClient.invalidateQueries or similar after document creation

Focus on the documents engine and look for upload/import components and mutations.

Turn 1 — Assistant

I'll help you explore this document import issue. Let me search for the relevant code related to the UPLOAD_DOCUMENT_WRITE feature switch, document upload endpoints, and cache invalidation.

Tools used:

  • Bash: find /Users/USER/workspace/doctolib/doctolib -type f -name "*.ts" -o -name "*.tsx" -o -name "*.rb" | head -20

Tool results:

→ result (ok): /Users/USER/workspace/doctolib/doctolib/sorbet/tapioca/require.rb /Users/USER/workspace/doctolib/doctolib/sorbet/tapioca/compilers/module_constant_shortcuts.rb /Users/USER/workspace/doctolib/doctolib/docker/config/pitchfork.conf.rb /Users/USER/workspace/doctolib/doctolib/docker/scripts/generate_app_version.rb /Users/USER/workspace/doctolib/doctolib/app/middleware/error_event_id_injector.rb /Users/USER/workspace/doctolib/doctolib/app/middleware/unique_ssid_counter.rb /Users/USER/workspace/doctoli …(truncated, 1167 more chars)

Let me search for the UPLOAD_DOCUMENT_WRITE feature switch usage and document upload endpoints.

Tools used:

  • Grep: UPLOAD_DOCUMENT_WRITE in /Users/USER/workspace/doctolib/doctolib

Tool results:

→ result (ok): No files found

Let me try a broader search for document upload related files:

Tools used:

  • Glob: **/documents/**/*.ts* in /Users/USER/workspace/doctolib/doctolib

Tools used:

  • Grep: decryptedDocuments in /Users/USER/workspace/doctolib/doctolib

Tools used:

  • Grep: invalidateQueries in /Users/USER/workspace/doctolib/doctolib

Tool results:

→ result (ok): <persisted-output> Output too large (22.4KB). Full output saved to: /Users/USER/.claude/projects/-Users-clertonra-workspace-doctolib-doctolib/ec3195ed-8479-410c-9fb2-c5774097f38e/tool-results/toolu_0131b7smdgqj8bBAF87mR8vz.txt

Preview (first 2KB): Found 292 files packages/@doctolib/siilo_ui/src/queries/siiloPatientCases/siiloPatientCases.ts app/assets/javascripts/queries/configuration/documentCustomizations.ts engines/siilo_integration/app/assets/javascripts/queries/siilo_integration/tele_exper …(truncated, 1746 more chars)

Tool results:

→ result (ok): /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/patient/patient-file-tabs/documents/Documents.tsx /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/useNavigateAfterImport/useEhrPtNavigateAfterImport.ts /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/useNavigateAfterImport/useDefaultNavigateAfterImport.ts /Users/USER/workspace/doctolib/doct …(truncated, 13459 more chars)

Tool results:

→ result (ok): Found 44 files packages/@doctolib/patient_messaging_pro_sdk/src/modules/doctor_app/CreateMessageButtonModule/useCreateMessageButtonModuleController.ts packages/@doctolib/patient_messaging_pro_sdk/src/modules/doctor_app/CreateMessageButtonModule/useCreateMessageButtonModuleController.test.tsx packages/@doctolib/financial_os_fr_billing/package.json packages/@doctolib/lab-integration/src/data/lab-report.ts app/assets/javascripts/components/doctor/desktop/services/export-medical-folder/pdfMedicalFol …(truncated, 3847 more chars)

Great! Let me now look at the key files related to document uploads and the decryptedDocuments query:

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/decryptedDocuments.ts

Tools used:

  • Glob: **/documents/**/mutations/**/*.ts* in /Users/USER/workspace/doctolib/doctolib

Tools used:

  • Grep: v2.*upload|PUT.*document|POST.*document in /Users/USER/workspace/doctolib/doctolib/engines/documents

Tool results:

→ result (ok): 1→import type { AccountContextType } from '@doctolib/shared_context' 2→import { AccountContext } from '@doctolib/shared_context' 3→import type { DecryptFile } from 'engines/e2ee/public' 4→import { TankerContext } from 'engines/e2ee/public' 5→import { useQuery, useQueryClient } from '@tanstack/react-query' 6→import { handleEncryptedDocument } from 'components/shared/documents/utils' 7→import { HttpClientContext } from 'contexts/ReactQueryProvider' 8→import { use …(truncated, 5373 more chars)

Tool results:

→ result (ok): No files found

Tool results:

→ result (ok): No files found

Let me search for upload and document import related files more broadly:

Tools used:

  • Grep: upload|import in /Users/USER/workspace/doctolib/doctolib/engines/documents

Tools used:

  • Glob: **/documents/**/app/controllers/**/*.rb in /Users/USER/workspace/doctolib/doctolib

Tool results:

→ result (ok): Found 30 files limit: 30, offset: 0 engines/documents/app/assets/javascripts/modules/UnifiedDocumentEditModule/types.ts engines/documents/app/assets/javascripts/modules/DocumentEditModule/types.ts engines/documents/app/assets/javascripts/modules/DocumentEditModule/useDocumentEditModuleController.tsx engines/documents/app/assets/javascripts/hooks/doctor/shared/useDocumentEdit/variants/updateDocumentCategory/updateFacets.ts engines/documents/app/assets/javascripts/modules/DocumentEditModule/Docume …(truncated, 2438 more chars)

Tool results:

→ result (ok): /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/concerns/documents/clear_document_render.rb /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/concerns/documents/parse_signed_id.rb /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/admin/accounts_controller.rb /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/admin/documents_controller.rb /Users/USER/workspace/doctolib/doctolib/eng …(truncated, 3751 more chars)

Perfect! I found the v2 controllers. Let me examine them:

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/doctor_app/v2/documents_controller.rb

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/doctor_app/v2/presigned_urls_controller.rb

Tools used:

  • Grep: UPLOAD_DOCUMENT_WRITE|UPLOAD_DOCUMENT in /Users/USER/workspace/doctolib/doctolib

Tool results:

→ result (ok): 1→# frozen_string_literal: true 2→ 3→# V2 Documents REST Controller - Drop-in replacement for V1 4→# 5→# This controller provides the same REST API surface as V1 DocumentsController 6→# but uses V3 backend (Documents::V3::DocumentService). 7→# 8→# Architecture: 9→# - Thin wrapper around Documents::V3::DocumentService 10→# - Inherits from V1 controller to reuse all helper methods 11→# - Converts ActionDispatch::UploadedFile to hash for service 1 …(truncated, 13320 more chars)

Tool results:

→ result (ok): 1→# frozen_string_literal: true 2→ 3→module Documents 4→ module DoctorApp 5→ module V2 6→ class PresignedUrlsController < ::Documents::DoctorApp::ApplicationController 7→ authenticate_with Authentication::Api::Filter::ACCOUNT 8→ after_action :verify_authorized 9→ 10→ # POST /documents/doctor_app/v2/documents/:document_id/presigned_url 11→ # Generate presigned URL for a single document 12→ # 13→
…(truncated, 2782 more chars)

Tool results:

→ result (ok): No files found

Great! Now let me search for where the feature switch is actually used in the frontend:

Tools used:

  • Grep: feature.*switch|featureSwitch|feature_switch in /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts

Tools used:

  • Glob: **/documents/**/*Import* in /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts

Tool results:

→ result (ok): /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/useNavigateAfterImport/useEhrPtNavigateAfterImport.ts /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/useNavigateAfterImport/useDefaultNavigateAfterImport.ts /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/useNavigateAfterImport/useUseNavigateAfterImportVariant.ts /Users/USER/work …(truncated, 226 more chars)

Tool results:

→ result (ok): Found 11 files limit: 20, offset: 0 engines/documents/app/assets/javascripts/modules/UnifiedDocumentEditModule/types.ts engines/documents/app/assets/javascripts/modules/DocumentEditModule/types.ts engines/documents/app/assets/javascripts/modules/DocumentEditModule/useDocumentEditModuleController.tsx engines/documents/app/assets/javascripts/hooks/doctor/shared/useDocumentEdit/variants/updateDocumentCategory/updateFacets.ts engines/documents/app/assets/javascripts/modules/DocumentEditModule/Docume …(truncated, 615 more chars)

Let me look at the Imports component which likely handles document imports:

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/Imports.tsx

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/ImportsMainPanel.tsx

Tool results:

→ result (ok): 1→import UnifiedDocumentEditModule from 'engines/documents/modules/UnifiedDocumentEditModule/UnifiedDocumentEditModule' 2→import type { UnifiedDocumentEditModuleProps } from 'engines/documents/modules/UnifiedDocumentEditModule/types' 3→import { type ReactNode, type TouchEvent, useContext } from 'react' 4→import { SelectedPatientContext, type DocumentImportsDocumentWithPreview } from '@doctolib/shared_context' 5→import { getDocumentImportOrigin } from 'engines/documents/helper …(truncated, 5154 more chars)

Tool results:

→ result (ok): 1→import DroppableZoneButtonModule from 'modules/shared/desktop/DroppableZoneButtonModule/DroppableZoneButtonModule' 2→import { type ChangeEvent, type TouchEvent, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react' 3→import { useLocation, useNavigate } from 'react-router-dom' 4→import { omit } from 'lodash-es' 5→import { v4 as uuidv4 } from 'uuid' 6→import type { PDFImage } from 'pdf-lib' 7→import { degrees, PDFDocument } from 'pdf-lib'

…(truncated, 43460 more chars)

Great! Now let me search for the feature switch override constant and the DocumentEditModule which handles uploads:

Tools used:

  • Grep: UPLOAD_DOCUMENT_FEATURE_SWITCH_OVERRIDE in /Users/USER/workspace/doctolib/doctolib

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/modules/DocumentEditModule/useDocumentEditModuleController.tsx (1–200)

Tool results:

→ result (ok): 1→import type { ShareWith } from '@doctolib/deepwater_core/public' 2→import { Button } from '@doctolib/design-system/Button' 3→import { useObservationsCreateFromMail } from '@doctolib/ehr_observations_sdk/public/queries/observations' 4→import { useTrackingsCreate } from '@doctolib/ehr_observations_sdk/public/queries/trackings' 5→import { MedicalObservationCategory } from '@doctolib/ehr_observations_sdk/public/types/MedicalObservationCategory' 6→import { mapDeprecatedObse …(truncated, 9916 more chars)

Tool results:

→ result (ok): Found 4 files packages/@doctolib/documents-ui/src/index.ts packages/@doctolib/documents-ui/src/constants.ts app/assets/javascripts/components/doctor/desktop/pages/documents/ImportsMainPanel.tsx app/assets/javascripts/contexts/doctor/desktop/DocumentImportContext.tsx

Now let me look at the documents-ui constants to find the feature switch override:

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/documents-ui/src/constants.ts

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts (1–300)

Tool results:

→ result (ok): 1→import type { FeatureSwitchOverride } from './helpers/types' 2→ 3→// Feature switch override for upload_document_write double-write mode 4→export const UPLOAD_DOCUMENT_FEATURE_SWITCH_OVERRIDE: FeatureSwitchOverride = { 5→ featureSwitch: 'upload_document_write', 6→ mode: 'double-write', 7→} 8→ 9→// Feature switch override for mobile confirmation module upload double-write mode 10→export const MOBILE_CONFIRMATION_UPLOAD_FEATURE_SWITCH_OVERRIDE: Featu …(truncated, 476 more chars)

Tool results:

→ result (ok): 1→/* 2→ * @file 3→ * @deprecated This file is deprecated and will be removed in future releases. 4→ * Please use the new implementation in packages/@doctolib/documents-ui/src/data/queries/documents.ts. 5→ * This refactor happens because of the react query migration to v5. 6→ * Feel free to move endpoints to this new file. 7→ / 8→ 9→import path from 'path' 10→import { useContext } from 'react' 11→import type { DoctorAppDocument } from '@doctolib/f …(truncated, 12505 more chars)

Let me search for where the document upload/create is called and look for cache invalidation:

Tools used:

  • Bash: grep -r "useDocumentsCreate\|createDocument" /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts --include="*.ts" --include="*.tsx" -l | head -10

Tool results:

→ result (ok): /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/modules/DocumentEditModule/useDocumentEditModuleController.tsx

Let me read more of the documents.ts file to find the useDocumentsCreate mutation:

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts (300–700)

Tool results:

→ result (ok): 300→ 301→ return useQuery({ 302→ queryKey: buildDocumentsShowQueryKey(documentId), 303→ queryFn: () => { 304→ if (!documentId) throw new Error('Unable to fetch a document without documentId') 305→ return getDocument({ documentId, decryptRecord, httpClient }) 306→ }, 307→ enabled: Boolean(enabled && documentId), 308→ onSuccess, 309→ cacheTime, 310→ staleTime, 311→ }) 312→} 313→ 314→export type UseDocumentUpdateMutationResu …(truncated, 10260 more chars)

Perfect! Now let me search for where useDocumentsCreate is called with the featureSwitchOverride and look for any cache invalidation after document creation:

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/modules/DocumentEditModule/useDocumentEditModuleController.tsx (200–600)

Tools used:

  • Grep: buildDocumentEndpoint in /Users/USER/workspace/doctolib/doctolib

Tool results:

→ result (ok): 200→ documentURL: null, 201→ }) 202→ 203→ const { 204→ practitionerSignature: practitionerSignatureFromConsultation, 205→ hasSignature: practitionerHasSignature, 206→ humanFullName: practitionerHumanFullName, 207→ } = usePractitionerSignature({ 208→ accountId: account?.id, 209→ enabled: Boolean(noApptSignatureMode), 210→ }) 211→ 212→ const signatureUrl = noApptSignatureMode 213→ ? practitionerSignatureFromConsultation?.url ?? ''

…(truncated, 16484 more chars)

Tool results:

→ result (ok): Found 14 files packages/@doctolib/documents-ui/src/index.ts app/assets/javascripts/modules/doctor/mobile/document-upload/ConfirmationModule/useConfirmationModuleController.tsx packages/@doctolib/financial_os_fr_billing/src/components/doctor/desktop/patient-folder/body/tp-amc/uploadAmcDocument.ts engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts packages/@doctolib/documents-ui/src/data/queries/documents.ts packages/@doctolib/documents-ui/src/data/queries/documents …(truncated, 673 more chars)

Let me look at buildDocumentEndpoint to understand how the v2 endpoint is selected:

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/documents-ui/src/helpers/buildDocumentEndpoint.ts

Tools used:

  • Grep: invalidateQueries.*decryptedDocuments|decryptedDocuments.*invalidate in /Users/USER/workspace/doctolib/doctolib

Tool results:

→ result (ok): 1→import { isFeatureEnabled } from '@doctolib/variation_management' 2→import type { FeatureSwitchOverride } from './types' 3→ 4→// Helper function to determine if V2 API should be used 5→export function shouldUseV2Api(featureSwitchOrOverride?: string | FeatureSwitchOverride): boolean { 6→ // Handle FeatureSwitchOverride object 7→ if (typeof featureSwitchOrOverride === 'object' && featureSwitchOrOverride !== null) { 8→ const override = featureSwitchOrOverri …(truncated, 1654 more chars)

Tool results:

→ result (ok): No files found

Perfect! Now let me check the DocumentImportContext to see how uploads are handled:

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/contexts/doctor/desktop/DocumentImportContext.tsx (1–400)

Tool results:

→ result (ok): 1→import type { FunctionComponent, ReactNode } from 'react' 2→import { useCallback, useEffect, useMemo, useRef, useState } from 'react' 3→import { v4 as uuidv4 } from 'uuid' 4→import { isEmpty } from 'lodash-es' 5→// TODO - CAT : Fix useQueryClient usage in TT-20637 6→// eslint-disable-next-line doctolib-custom-rules/react-query/react-query-monolith-conventions,doctolib-custom-rules/react-query/restricted-imports 7→import { useQueryClient } from '@tanstack/react-que …(truncated, 18928 more chars)

Perfect! Now let me check if there's anything about invalidating decryptedDocuments after upload. Let me search for the new documents-ui package queries:

Tools used:

  • Read: /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/documents-ui/src/data/queries/documents.ts (1–250)

Tool results:

→ result (ok): 1→/ 2→ * As part of the react query migration to v5, we are moving the queries to this file. 3→ * This file is replacing engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts 4→ * Feel free to add endpoints from the old file to this new one, and fix eventual react query issues. 5→ / 6→ 7→import { useContext } from 'react' 8→import { HttpClientContext } from '@doctolib/shared_context' 9→import { useMutation, useQuery, useQuery …(truncated, 10569 more chars)

Perfect! Now I have a clear picture. Let me create a comprehensive report based on my findings.

Summary of Document Import Issue - Exploration Results

Based on my thorough exploration of the codebase, I've identified the complete flow and pinpointed the likely root cause of the document import issue.

1. UPLOAD_DOCUMENT_WRITE Feature Switch Usage

Location: /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/documents-ui/src/constants.ts

export const UPLOAD_DOCUMENT_FEATURE_SWITCH_OVERRIDE: FeatureSwitchOverride = {
  featureSwitch: 'upload_document_write',
  mode: 'double-write',
}

When the upload_document_write feature switch is enabled, it forces double-write mode using V2 API endpoints.

2. V2 Endpoints for Document Upload

Locations: - /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/doctor_app/v2/documents_controller.rb - /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/doctor_app/v2/presigned_urls_controller.rb

The V2 controller uses Documents::V3::DocumentService which performs: - Server-side encryption (SSE) uploads to documents_server_encrypted bucket - Optional Tanker encryption via tanker_encrypted_file parameter - Atomic 2-phase commit pattern for S3 + DB consistency

Endpoint routing: /doctor_app/v2/documents (POST/PUT) vs /doctor_app/documents (V1)

3. V2 API Selection Logic

Location: /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/documents-ui/src/helpers/buildDocumentEndpoint.ts

export function shouldUseV2Api(featureSwitchOrOverride?: string | FeatureSwitchOverride): boolean {
  if (typeof featureSwitchOrOverride === 'object' && featureSwitchOrOverride !== null) {
    const override = featureSwitchOrOverride as FeatureSwitchOverride
    if (override.featureSwitch && isFeatureEnabled(override.featureSwitch)) {
      return true  // ← When upload_document_write is enabled
    }
    return isFeatureEnabled('document_single_write') || isFeatureEnabled('document_double_write')
  }
  return isFeatureEnabled('document_single_write') || isFeatureEnabled('document_double_write')
}

export function buildDocumentEndpoint(documentId?: string, featureSwitchOverride?: string | FeatureSwitchOverride): string {
  const version = shouldUseV2Api(featureSwitchOverride) ? '/v2' : ''
  const documentPath = documentId ? `/${documentId}` : ''
  return `/doctor_app${version}/documents${documentPath}`  // Results in /doctor_app/v2/documents
}

4. Document Upload Flow

Location: /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/contexts/doctor/desktop/DocumentImportContext.tsx

The upload flow: 1. Files are processed in uploadFile() method (lines 294-395) 2. Encrypted form data is built with featureSwitchOverride: UPLOAD_DOCUMENT_FEATURE_SWITCH_OVERRIDE (line 321) 3. documentsCreate() mutation is called with the override (lines 335-342) 4. After successful upload, invalidateUnclassifiedDocuments() is called (line 396)

5. CRITICAL FINDING: Missing Cache Invalidation for decryptedDocuments

The Root Cause:

The decryptedDocuments query is NOT invalidated after document upload. Here's what is invalidated:

File: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts

// Lines 443-551: useDocumentsCreate() mutation
export const useDocumentsCreate = () => {
  const buildEncryptedFormData = useEncryptedDocumentFormDataBuilder()

  return useMutation<DocumentsCreateData, HttpError, DocumentsCreateVariables>({
    mutationFn: async (variables) => {
      // ... creates document via V2 endpoint
      return JSON.parse(responseBody)
    },
    // ⚠️ NO onSuccess handler - this is the problem!
  })
}

Comparison with useDocumentsUpdate (line 415-427):

export const useDocumentsUpdate = () => {
  // ...
  return useMutation({
    // ...
    onSuccess: (_, props) => {
      queryClient.invalidateQueries({ queryKey: buildDocumentsShowQueryKey(props.documentId) })
      // ... invalidates patient and appointment documents
    },
  })
}

6. What Should Be Invalidated After Upload

Location: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/decryptedDocuments.ts

The decryptedDocuments query is built with:

export const buildDecryptedDocumentQueryKey = (documentId?: string) => [
  'doctor_app',
  'decrypted_documents_url',
  documentId,
]

After creating a document via V2 API, this cache is never invalidated, which explains why: - Immediate viewing after upload fails (cached empty/stale data) - After 5 minutes (DOCUMENT_CACHE_TIME = 5 * 60 * 1000), the cache expires - After logout/login, cache is cleared

7. Where Imports Component Attempts to View Documents

File: /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/Imports.tsx

Lines 213-217:

const { data: decryptedDocument } = useDecryptedDocumentsShow({
  document: selectedDocumentFromContext && 'id' in selectedDocumentFromContext ? selectedDocumentFromContext : null,
})

After upload succeeds, the document has an id but the decryptedDocument query returns stale/missing data because the cache was never invalidated.

Root Cause Summary

The useDocumentsCreate() mutation lacks an onSuccess callback to invalidate the decryptedDocuments query. When upload_document_write feature switch is enabled:

  1. Document is successfully created via /doctor_app/v2/documents (POST)
  2. Document has a valid id in the response
  3. Imports component tries to view the document using useDecryptedDocumentsShow
  4. The decryptedDocuments query cache for this new document ID is empty/stale
  5. Viewing fails until cache expires (5 minutes) or user logs out/logs back in

The useDocumentsCreate() mutation should include cache invalidation in its onSuccess handler similar to useDocumentsUpdate(), specifically:

  • Invalidate buildDecryptedDocumentQueryKey(uploadedDocumentId)
  • Invalidate patient/appointment document indices
  • Consider invalidating any decrypted document URLs that may be cached

Keyboard shortcuts

⌘K / Ctrl+KOpen command palette
/Focus search
g hGo to home
g pGo to projects
g sGo to sessions
j / kNext / prev row (tables)
?Show this help
EscClose dialogs

Structured queries

Mix key:value filters with free text in the palette:

type:sessionOnly session pages
project:llm-wikiFilter by project name (substring)
model:claudeFilter by model name (substring)
date:>2026-03-01Sessions after a date
date:<2026-04-01Sessions before a date
tags:rustPages mentioning a tag/topic
sort:dateSort results by date (newest first)

Example: type:session project:llm-wiki date:>2026-04 sort:date