Chrome permissions disappearing unexpectedly in Dash app, how to fix this?

Problem:

My Plotly Dash app (python) has a clientside callback (javascript) which prompts the user to select a folder, then saves a file in a subfolder within that folder. Chrome asks for permission to read and write to the folder, which is fine, but I want the user to only have to give permission once. Unfortunately the permissions, which should persist until the tab closes, disappear often. Two “repeatable cases” are:

  • when the user clicks a simple button ~15 times very fast, previously accepted permissions will disappear (plotting a figure also does this in my real application)
  • downloading a file within a few seconds of reloading the page results in the permissions automatically going away within about 5 seconds

I can see the permissions (file and pen icon) disappear at the right of the chrome url banner.

What I’ve tried:

  • testing with Ublock Origin on/off (and removed from chrome) to see if the extension interfered (got idea from the only somewhat similar question I’ve come across: window.confirm disappears without interaction in Chrome)
  • turning debug mode off
  • using Edge instead of chrome (basically the same behavior was observed)
  • adding more computation to Test button to find repeatable case, but still needed to click it a lot to remove permissions (triggering callbacks / updating Dash components seems to be the issue, not server resources)
  • running the javascript part in a different web app (unrelated to plotly dash) which worked and left the permissions there until tab is closed

Example python script (dash app) to show permissions disappearing:

import dash
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
from dash import html

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = html.Div([
    dbc.Button(id="model-export-button", children="Export Model"),
    dbc.Label(id="test-label1", children="Click to download"),
    html.Br(),
    dbc.Button(id="test-button", children="Test button"),
    dbc.Label(id="test-label2", children="Button not clicked")
])

# Chrome web API used for downloading: https://web.dev/file-system-access/
app.clientside_callback(
    """
    async function(n_clicks) {
        // Select directory to download
        const directoryHandle = await window.showDirectoryPicker({id: 'save-dir', startIn: 'downloads'});
        
        // Create sub-folder in that directory
        const newDirectoryHandle = await directoryHandle.getDirectoryHandle("test-folder-name", {create: true});
        
        // Download files to sub-folder
        const fileHandle = await newDirectoryHandle.getFileHandle("test-file-name.txt", {create: true});
        const writable = await fileHandle.createWritable();
        await writable.write("Hello world.");
        await writable.close();
        
        // Create status message
        const event = new Date(Date.now());
        const msg = "File(s) saved successfully at " + event.toLocaleTimeString();
        return msg;
    }
    """,
    Output('test-label1', 'children'),
    Input('model-export-button', 'n_clicks'),
    prevent_initial_call=True
)

@app.callback(
    Output('test-label2', 'children'),
    Input('test-button', 'n_clicks'),
    prevent_initial_call=True
)
def test_button_function(n):
    return "Button has been clicked " + str(n) + " times"

if __name__ == "__main__":
    app.run_server(debug=False)

Also asked here: javascript - View and edit file permissions disappear unexpectedly in Chrome with Plotly Dash - Stack Overflow