Skip to content

Support auto-rerun when config.toml is created#13625

Merged
lukasmasuch merged 7 commits intodevelopfrom
lukasmasuch/load-config-on-creation
Jan 21, 2026
Merged

Support auto-rerun when config.toml is created#13625
lukasmasuch merged 7 commits intodevelopfrom
lukasmasuch/load-config-on-creation

Conversation

@lukasmasuch
Copy link
Copy Markdown
Collaborator

Describe your changes

When Streamlit starts without a config.toml file, creating one now triggers an automatic app rerun to apply the configuration. This addresses the issue where newly created config files were not recognized without a manual server restart.

The implementation watches parent .streamlit directories for file creation using watch_dir() with a glob pattern when the config file doesn't exist at startup. The existing callback mechanism reloads configuration upon file creation.

GitHub Issue Link

Fixes #11296

Testing Plan

  • Unit Tests: Added two comprehensive test cases:
    • test_install_config_watcher_for_nonexistent_files: Verifies directory watching when config files don't exist
    • test_install_config_watcher_mixed_existing_and_nonexistent: Tests mixed scenarios with existing and non-existing files
  • Manual Testing: Create a config file while app is running to verify automatic rerun
  • No regression: Existing behavior for watching existing config files remains unchanged

Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

Watch parent .streamlit directories for config file creation when the file doesn't exist at startup. This allows Streamlit to automatically detect and apply newly created config files without requiring a server restart.

Fixes #11296

Co-Authored-By: Claude <[email protected]>
Copilot AI review requested due to automatic review settings January 17, 2026 12:55
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Jan 17, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 17, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-13625/streamlit-1.53.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-13625.streamlit.app (☁️ Deploy here if not accessible)

@lukasmasuch lukasmasuch changed the title Support auto-rerun when config.toml is created Support auto-rerun when config.toml is created Jan 17, 2026
@lukasmasuch lukasmasuch added security-assessment-completed change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users ai-review If applied to PR or issue will run AI review workflow labels Jan 17, 2026
@lukasmasuch
Copy link
Copy Markdown
Collaborator Author

@cursor review

@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Jan 17, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR adds support for auto-rerun when config.toml is created at runtime. When Streamlit starts without a config.toml file, creating one will now trigger an automatic app rerun to apply the configuration. The implementation modifies _install_config_watchers() in bootstrap.py to use watch_dir() for watching parent directories when config files don't exist, complementing the existing watch_file() behavior for existing files.

Changed files:

  • lib/streamlit/web/bootstrap.py - Added directory watching logic
  • lib/tests/streamlit/web/bootstrap_test.py - Added two new test cases

Code Quality

The implementation is clean, minimal, and follows existing patterns in the codebase:

Strengths:

  • Uses the existing watch_dir() infrastructure with appropriate parameters (glob_pattern="config.toml", allow_nonexistent=True)
  • The code is well-commented, explaining the purpose of each branch (lines 329, 331)
  • Import is properly added at the top of the file

Minor Issue:

In test_install_config_watcher_mixed_existing_and_nonexistent (lines 452-454), there is dead code - the exists_side_effect function is defined but never used. The test actually uses custom_exists instead:

def exists_side_effect(path):
    # Simulate: first config file exists, second doesn't
    return "home" in path or path.endswith("0")

This unused function should be removed to keep the test clean.

Test Coverage

The test coverage is adequate with two new test cases:

  1. test_install_config_watcher_for_nonexistent_files (lines 413-442):

    • Verifies watch_dir is called for each config file location when files don't exist
    • Verifies watch_file is NOT called (good negative assertion)
    • Verifies correct parameters (glob_pattern, allow_nonexistent)
    • Verifies callback triggers config reload
  2. test_install_config_watcher_mixed_existing_and_nonexistent (lines 444-469):

    • Tests the mixed scenario where some files exist and some don't
    • Verifies correct dispatch between watch_file and watch_dir

The tests follow best practices:

  • Include docstrings explaining the test purpose
  • Use appropriate mocking
  • Include negative assertions (e.g., assert patched_watch_file.call_count == 0)

Backwards Compatibility

This change is fully backwards compatible:

  • Existing behavior for watching existing config files remains unchanged (watch_file is still used when files exist)
  • The new directory watching only activates when config files don't exist at startup
  • No API changes or breaking modifications

Security & Risk

Low risk:

  • Uses existing, well-tested watch_dir() infrastructure
  • No new attack vectors introduced
  • The file watcher only triggers config reload, which is already a supported operation
  • Directory watching with allow_nonexistent=True is designed for this exact use case

Edge case consideration:
After verifying the watcher implementation in event_based_path_watcher.py, I confirmed that subsequent modifications to config.toml after creation will also trigger the callback, ensuring the feature works correctly for the full lifecycle.

Recommendations

  1. Remove dead code in test (Minor): Remove the unused exists_side_effect function in test_install_config_watcher_mixed_existing_and_nonexistent (line 452-454). This is optional but improves code cleanliness.

Verdict

APPROVED: This is a well-implemented feature that addresses issue #11296 with minimal, clean code changes. The implementation correctly uses existing infrastructure, includes adequate test coverage, and poses no backwards compatibility or security concerns. The only minor issue is a small amount of dead code in the test file that can be addressed as a follow-up if desired.


This is an automated AI review. Please verify the feedback and use your judgment.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds support for automatic app rerun when a config.toml file is created while Streamlit is running. Previously, creating a config file after server startup required a manual restart.

Changes:

  • Modified _install_config_watchers() to watch parent .streamlit directories when config files don't exist
  • Added watch_dir import to bootstrap.py
  • Added comprehensive unit tests for both scenarios: when config files don't exist and mixed scenarios

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
lib/streamlit/web/bootstrap.py Added logic to watch parent directories with glob pattern when config files don't exist at startup
lib/tests/streamlit/web/bootstrap_test.py Added two unit tests to verify directory watching for non-existent config files and mixed scenarios

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 17, 2026

📉 Python coverage change detected

The Python unit test coverage has decreased by 0.0007%

  • Current PR: 93.0539% (22833 statements, 1586 missed)
  • Latest develop: 93.0546% (22821 statements, 1585 missed)

✅ Coverage change is within normal range.

Coverage by files
Name Stmts Miss Cover
streamlit/__init__.py 136 0 100%
streamlit/__main__.py 3 3 0%
streamlit/auth_util.py 231 25 89%
streamlit/cli_util.py 39 6 85%
streamlit/column_config.py 3 0 100%
streamlit/commands/__init__.py 0 0 100%
streamlit/commands/echo.py 54 2 96%
streamlit/commands/execution_control.py 70 10 86%
streamlit/commands/logo.py 41 1 98%
streamlit/commands/navigation.py 106 2 98%
streamlit/commands/page_config.py 106 4 96%
streamlit/components/__init__.py 0 0 100%
streamlit/components/lib/__init__.py 0 0 100%
streamlit/components/lib/local_component_registry.py 35 2 94%
streamlit/components/types/__init__.py 0 0 100%
streamlit/components/types/base_component_registry.py 14 0 100%
streamlit/components/types/base_custom_component.py 48 6 88%
streamlit/components/v1/__init__.py 5 0 100%
streamlit/components/v1/component_arrow.py 33 2 94%
streamlit/components/v1/component_registry.py 41 3 93%
streamlit/components/v1/components.py 4 4 0%
streamlit/components/v1/custom_component.py 84 7 92%
streamlit/components/v2/__init__.py 27 0 100%
streamlit/components/v2/bidi_component/__init__.py 4 0 100%
streamlit/components/v2/bidi_component/constants.py 5 0 100%
streamlit/components/v2/bidi_component/main.py 148 17 89%
streamlit/components/v2/bidi_component/serialization.py 81 2 98%
streamlit/components/v2/bidi_component/state.py 13 0 100%
streamlit/components/v2/component_definition_resolver.py 30 0 100%
streamlit/components/v2/component_file_watcher.py 117 9 92%
streamlit/components/v2/component_manager.py 97 13 87%
streamlit/components/v2/component_manifest_handler.py 24 0 100%
streamlit/components/v2/component_path_utils.py 68 5 93%
streamlit/components/v2/component_registry.py 121 8 93%
streamlit/components/v2/get_bidi_component_manager.py 8 1 88%
streamlit/components/v2/manifest_scanner.py 227 25 89%
streamlit/components/v2/presentation.py 84 19 77%
streamlit/components/v2/types.py 8 8 0%
streamlit/config.py 415 12 97%
streamlit/config_option.py 79 3 96%
streamlit/config_util.py 288 7 98%
streamlit/connections/__init__.py 6 0 100%
streamlit/connections/base_connection.py 49 0 100%
streamlit/connections/snowflake_connection.py 97 18 81%
streamlit/connections/snowpark_connection.py 44 3 93%
streamlit/connections/sql_connection.py 56 6 89%
streamlit/connections/util.py 33 0 100%
streamlit/cursor.py 130 1 99%
streamlit/dataframe_util.py 506 47 91%
streamlit/delta_generator.py 250 7 97%
streamlit/delta_generator_singletons.py 74 7 91%
streamlit/deprecation_util.py 59 4 93%
streamlit/development.py 1 0 100%
streamlit/elements/__init__.py 0 0 100%
streamlit/elements/alert.py 60 0 100%
streamlit/elements/arrow.py 203 15 93%
streamlit/elements/balloons.py 10 0 100%
streamlit/elements/bokeh_chart.py 9 0 100%
streamlit/elements/code.py 20 1 95%
streamlit/elements/deck_gl_json_chart.py 104 10 90%
streamlit/elements/dialog_decorator.py 38 0 100%
streamlit/elements/doc_string.py 227 9 96%
streamlit/elements/empty.py 16 4 75%
streamlit/elements/exception.py 101 10 90%
streamlit/elements/form.py 56 2 96%
streamlit/elements/graphviz_chart.py 36 1 97%
streamlit/elements/heading.py 56 0 100%
streamlit/elements/html.py 49 0 100%
streamlit/elements/iframe.py 29 0 100%
streamlit/elements/image.py 32 0 100%
streamlit/elements/json.py 48 6 88%
streamlit/elements/layouts.py 140 3 98%
streamlit/elements/lib/__init__.py 0 0 100%
streamlit/elements/lib/built_in_chart_utils.py 391 26 93%
streamlit/elements/lib/color_util.py 100 4 96%
streamlit/elements/lib/column_config_utils.py 169 1 99%
streamlit/elements/lib/column_types.py 190 4 98%
streamlit/elements/lib/dialog.py 69 1 99%
streamlit/elements/lib/dicttools.py 39 2 95%
streamlit/elements/lib/file_uploader_utils.py 30 0 100%
streamlit/elements/lib/form_utils.py 26 0 100%
streamlit/elements/lib/image_utils.py 176 21 88%
streamlit/elements/lib/js_number.py 28 3 89%
streamlit/elements/lib/layout_utils.py 121 1 99%
streamlit/elements/lib/mutable_status_container.py 73 4 95%
streamlit/elements/lib/options_selector_utils.py 119 0 100%
streamlit/elements/lib/pandas_styler_utils.py 80 2 98%
streamlit/elements/lib/policies.py 56 1 98%
streamlit/elements/lib/shortcut_utils.py 42 2 95%
streamlit/elements/lib/streamlit_plotly_theme.py 48 0 100%
streamlit/elements/lib/subtitle_utils.py 76 5 93%
streamlit/elements/lib/utils.py 76 5 93%
streamlit/elements/map.py 110 1 99%
streamlit/elements/markdown.py 65 2 97%
streamlit/elements/media.py 181 8 96%
streamlit/elements/metric.py 104 0 100%
streamlit/elements/pdf.py 49 2 96%
streamlit/elements/plotly_chart.py 129 6 95%
streamlit/elements/progress.py 36 0 100%
streamlit/elements/pyplot.py 39 2 95%
streamlit/elements/snow.py 10 0 100%
streamlit/elements/space.py 12 0 100%
streamlit/elements/spinner.py 44 3 93%
streamlit/elements/text.py 16 0 100%
streamlit/elements/toast.py 26 0 100%
streamlit/elements/vega_charts.py 238 3 99%
streamlit/elements/widgets/__init__.py 0 0 100%
streamlit/elements/widgets/audio_input.py 68 1 99%
streamlit/elements/widgets/button.py 245 6 98%
streamlit/elements/widgets/button_group.py 171 1 99%
streamlit/elements/widgets/camera_input.py 62 1 98%
streamlit/elements/widgets/chat.py 237 38 84%
streamlit/elements/widgets/checkbox.py 52 0 100%
streamlit/elements/widgets/color_picker.py 59 2 97%
streamlit/elements/widgets/data_editor.py 254 14 94%
streamlit/elements/widgets/file_uploader.py 108 10 91%
streamlit/elements/widgets/multiselect.py 109 2 98%
streamlit/elements/widgets/number_input.py 146 4 97%
streamlit/elements/widgets/radio.py 83 5 94%
streamlit/elements/widgets/select_slider.py 97 0 100%
streamlit/elements/widgets/selectbox.py 95 1 99%
streamlit/elements/widgets/slider.py 241 8 97%
streamlit/elements/widgets/text_widgets.py 130 6 95%
streamlit/elements/widgets/time_widgets.py 425 21 95%
streamlit/elements/write.py 166 20 88%
streamlit/emojis.py 4 0 100%
streamlit/env_util.py 21 3 86%
streamlit/error_util.py 33 2 94%
streamlit/errors.py 184 25 86%
streamlit/external/__init__.py 0 0 100%
streamlit/external/langchain/__init__.py 2 0 100%
streamlit/external/langchain/streamlit_callback_handler.py 141 82 42%
streamlit/file_util.py 84 8 90%
streamlit/git_util.py 100 5 95%
streamlit/logger.py 54 0 100%
streamlit/material_icon_names.py 1 0 100%
streamlit/navigation/__init__.py 0 0 100%
streamlit/navigation/page.py 78 2 97%
streamlit/net_util.py 55 3 95%
streamlit/platform.py 10 1 90%
streamlit/runtime/__init__.py 8 0 100%
streamlit/runtime/app_session.py 456 85 81%
streamlit/runtime/caching/__init__.py 21 0 100%
streamlit/runtime/caching/cache_data_api.py 191 3 98%
streamlit/runtime/caching/cache_errors.py 44 4 91%
streamlit/runtime/caching/cache_resource_api.py 165 1 99%
streamlit/runtime/caching/cache_type.py 11 1 91%
streamlit/runtime/caching/cache_utils.py 176 9 95%
streamlit/runtime/caching/cached_message_replay.py 108 1 99%
streamlit/runtime/caching/hashing.py 310 25 92%
streamlit/runtime/caching/legacy_cache_api.py 14 0 100%
streamlit/runtime/caching/storage/__init__.py 2 0 100%
streamlit/runtime/caching/storage/cache_storage_protocol.py 29 0 100%
streamlit/runtime/caching/storage/dummy_cache_storage.py 21 0 100%
streamlit/runtime/caching/storage/in_memory_cache_storage_wrapper.py 67 1 99%
streamlit/runtime/caching/storage/local_disk_cache_storage.py 86 4 95%
streamlit/runtime/caching/ttl_cleanup_cache.py 28 0 100%
streamlit/runtime/connection_factory.py 96 11 89%
streamlit/runtime/context.py 137 0 100%
streamlit/runtime/context_util.py 18 0 100%
streamlit/runtime/credentials.py 139 4 97%
streamlit/runtime/download_data_util.py 27 0 100%
streamlit/runtime/forward_msg_cache.py 23 2 91%
streamlit/runtime/forward_msg_queue.py 63 4 94%
streamlit/runtime/fragment.py 112 2 98%
streamlit/runtime/media_file_manager.py 110 7 94%
streamlit/runtime/media_file_storage.py 15 0 100%
streamlit/runtime/memory_media_file_storage.py 73 0 100%
streamlit/runtime/memory_session_storage.py 15 0 100%
streamlit/runtime/memory_uploaded_file_manager.py 46 1 98%
streamlit/runtime/metrics_util.py 195 13 93%
streamlit/runtime/pages_manager.py 59 2 97%
streamlit/runtime/runtime.py 253 16 94%
streamlit/runtime/runtime_util.py 30 1 97%
streamlit/runtime/script_data.py 16 0 100%
streamlit/runtime/scriptrunner/__init__.py 5 0 100%
streamlit/runtime/scriptrunner/exec_code.py 49 5 90%
streamlit/runtime/scriptrunner/magic.py 83 1 99%
streamlit/runtime/scriptrunner/magic_funcs.py 10 1 90%
streamlit/runtime/scriptrunner/script_cache.py 27 0 100%
streamlit/runtime/scriptrunner/script_runner.py 230 27 88%
streamlit/runtime/scriptrunner_utils/__init__.py 0 0 100%
streamlit/runtime/scriptrunner_utils/exceptions.py 9 1 89%
streamlit/runtime/scriptrunner_utils/script_requests.py 106 5 95%
streamlit/runtime/scriptrunner_utils/script_run_context.py 123 2 98%
streamlit/runtime/secrets.py 241 25 90%
streamlit/runtime/session_manager.py 71 2 97%
streamlit/runtime/state/__init__.py 7 0 100%
streamlit/runtime/state/common.py 52 2 96%
streamlit/runtime/state/presentation.py 19 4 79%
streamlit/runtime/state/query_params.py 117 4 97%
streamlit/runtime/state/query_params_proxy.py 71 0 100%
streamlit/runtime/state/safe_session_state.py 77 9 88%
streamlit/runtime/state/session_state.py 440 33 92%
streamlit/runtime/state/session_state_proxy.py 62 8 87%
streamlit/runtime/state/widgets.py 15 1 93%
streamlit/runtime/stats.py 132 4 97%
streamlit/runtime/theme_util.py 46 1 98%
streamlit/runtime/uploaded_file_manager.py 39 3 92%
streamlit/runtime/websocket_session_manager.py 116 0 100%
streamlit/source_util.py 36 1 97%
streamlit/starlette.py 2 0 100%
streamlit/string_util.py 93 9 90%
streamlit/temporary_directory.py 18 1 94%
streamlit/testing/__init__.py 0 0 100%
streamlit/testing/v1/__init__.py 2 0 100%
streamlit/testing/v1/app_test.py 242 5 98%
streamlit/testing/v1/element_tree.py 1370 81 94%
streamlit/testing/v1/local_script_runner.py 71 2 97%
streamlit/testing/v1/util.py 17 0 100%
streamlit/time_util.py 28 1 96%
streamlit/type_util.py 148 16 89%
streamlit/url_util.py 39 4 90%
streamlit/user_info.py 105 8 92%
streamlit/util.py 38 1 97%
streamlit/version.py 3 0 100%
streamlit/watcher/__init__.py 3 0 100%
streamlit/watcher/event_based_path_watcher.py 184 25 86%
streamlit/watcher/folder_black_list.py 14 1 93%
streamlit/watcher/local_sources_watcher.py 127 9 93%
streamlit/watcher/path_watcher.py 42 3 93%
streamlit/watcher/polling_path_watcher.py 55 2 96%
streamlit/watcher/util.py 59 1 98%
streamlit/web/__init__.py 0 0 100%
streamlit/web/bootstrap.py 174 21 88%
streamlit/web/cache_storage_manager_config.py 5 0 100%
streamlit/web/cli.py 188 16 91%
streamlit/web/server/__init__.py 5 0 100%
streamlit/web/server/app_discovery.py 104 5 95%
streamlit/web/server/app_static_file_handler.py 29 3 90%
streamlit/web/server/authlib_tornado_integration.py 42 5 88%
streamlit/web/server/bidi_component_request_handler.py 65 8 88%
streamlit/web/server/browser_websocket_handler.py 147 18 88%
streamlit/web/server/component_file_utils.py 24 0 100%
streamlit/web/server/component_request_handler.py 55 4 93%
streamlit/web/server/media_file_handler.py 65 9 86%
streamlit/web/server/oauth_authlib_routes.py 162 33 80%
streamlit/web/server/oidc_mixin.py 46 0 100%
streamlit/web/server/routes.py 90 7 92%
streamlit/web/server/server.py 195 13 93%
streamlit/web/server/server_util.py 68 5 93%
streamlit/web/server/starlette/__init__.py 3 0 100%
streamlit/web/server/starlette/starlette_app.py 146 4 97%
streamlit/web/server/starlette/starlette_app_utils.py 101 7 93%
streamlit/web/server/starlette/starlette_auth_routes.py 233 51 78%
streamlit/web/server/starlette/starlette_gzip_middleware.py 30 0 100%
streamlit/web/server/starlette/starlette_routes.py 346 86 75%
streamlit/web/server/starlette/starlette_server.py 167 7 96%
streamlit/web/server/starlette/starlette_server_config.py 13 0 100%
streamlit/web/server/starlette/starlette_static_routes.py 64 3 95%
streamlit/web/server/starlette/starlette_websocket.py 203 23 89%
streamlit/web/server/stats_request_handler.py 59 5 92%
streamlit/web/server/upload_file_request_handler.py 59 7 88%
streamlit/web/server/websocket_headers.py 19 1 95%
TOTAL 22833 1586 93%

📊 View detailed coverage comparison

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no bugs!

else:
# Watch parent directory for file creation
parent_dir = os.path.dirname(filename)
watch_dir(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Will there be any unintended side-effects of this change? If I'm reading this correct, changing secrets.toml will now also trigger a re-run in this conditional branch, but not if config.toml already exists. That seems like unexpected behavior to me.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file watcher will receive events for all file changes in the directory, but I believe the glob_pattern="config.toml" here ensures that the callback itself is only triggered when it is touching the config.toml.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a quick deep dive with Claude. And there seems to be a bug with the event-based file watcher (watchdog) not correctly respecting this parameter, while the poll watcher works fine. I will add a fix for this and do a manual verification

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for confirming and the quick follow-up fixes!

When watching a directory with a glob_pattern, file events inside the
directory now correctly use the watched directory path for MD5
calculation instead of the individual file path. This ensures the
glob_pattern filtering is applied correctly.

Previously, changes to any file in a watched directory (e.g., secrets.toml)
could trigger callbacks even when only specific files (e.g., config.toml)
should be watched.

Co-Authored-By: Claude <[email protected]>
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR adds directory-based config watchers so config.toml creation is detected, and updates the event-based watcher to hash the watched directory for correct glob filtering. It also adds unit tests covering the new watcher behavior.

Code Quality

Changes are localized and follow existing patterns; the watched_path tracking makes the directory-vs-file behavior explicit. One consistency concern: for directory watches, the early mtime short-circuit still uses the event file path while the MD5 hash uses the watched directory, which could skip a real directory-level change on coarse timestamp filesystems (lib/streamlit/watcher/event_based_path_watcher.py lines 431-452).

Test Coverage

Unit tests cover the new directory glob filtering and file-creation detection behavior in the event-based watcher, plus the new config watcher logic (lib/tests/streamlit/watcher/event_based_path_watcher_test.py lines 520-653, lib/tests/streamlit/web/bootstrap_test.py lines 413-465). No frontend or e2e changes are required here.

Backwards Compatibility

No public API changes; behavior is internal to watcher plumbing. The callback path for directory watches now reflects the watched directory rather than the specific file event, but current internal uses don’t depend on the exact path.

Security & Risk

Low risk. The change is scoped to config file watching and watcher MD5 calculation logic, with tests in place.

Recommendations

  1. Consider using watched_path for the mtime check when the matched watch is a directory to keep the short-circuit consistent with the MD5 target and reduce the chance of missing changes on coarse mtime filesystems (lib/streamlit/watcher/event_based_path_watcher.py lines 431-452).

Verdict

APPROVED: Solid, scoped change with good unit coverage and minimal risk.


This is an automated AI review using gpt-5.2-codex-high. Please verify the feedback and use your judgment.

else:
# Watch parent directory for file creation
parent_dir = os.path.dirname(filename)
watch_dir(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for confirming and the quick follow-up fixes!

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 21, 2026

📈 Frontend coverage change detected

The frontend unit test (vitest) coverage has increased by 0.0500%

  • Current PR: 86.4500% (13339 lines, 1807 missed)
  • Latest develop: 86.4000% (13339 lines, 1814 missed)

🎉 Great job on improving test coverage!

📊 View detailed coverage comparison

@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR adds allow_nonexistent support for config watchers so creating config.toml triggers reloads, and adjusts event-based watcher MD5 handling to respect glob patterns. It also adds unit tests for watcher behavior and bootstrap config watcher wiring.

Code Quality

  • Potential regression: EventBasedPathWatcher.handle_path_change_event now passes watched_path to callbacks for directory events, which strips the actual changed file path (e.g., lib/streamlit/watcher/event_based_path_watcher.py lines 388-460). This breaks path-specific filtering logic such as ComponentFileWatcher._is_in_ignored_directory, which expects a file path to detect ignored directories like node_modules (e.g., lib/streamlit/components/v2/component_file_watcher.py lines 352-402). Consider keeping the callback argument as the actual changed file path while using watched_path only for MD5 calculations.
  • Potential regression: The new MD5 computation uses the watched directory path when a directory is watched (same block above), but directory MD5s in calc_md5_with_blocking_retries are based on filenames only (not file contents) (e.g., lib/streamlit/watcher/util.py lines 49-61, 106-111). This means edits to existing files inside watched directories will not trigger callbacks, which can break hot-reload for component asset directories or server.folderWatchList.

Test Coverage

Unit tests were added for event-based and polling watchers and the bootstrap config watcher wiring. However, there is no test asserting that file content changes inside watched directories still trigger callbacks when glob_pattern is used; given the MD5 change, this is a high-risk gap. No e2e or frontend tests are necessary for this backend-only change.

Backwards Compatibility

The callback argument change for directory watches and the loss of file-content change detection in watched directories can alter behavior in existing integrations (component watchers, folder watch list). This is a backwards compatibility risk and should be addressed before merge.

Security & Risk

No direct security issues identified. The main risk is regressions in file watching that can prevent reruns or reloads when files are edited, or trigger reloads for ignored directories.

Recommendations

  1. Preserve callback semantics for directory watches by sending the actual changed file path while using watched_path only for MD5 and logging (or provide both paths).
  2. Ensure directory watchers still detect file content modifications with glob_pattern (e.g., compute MD5 for matching files or include file mtimes/contents in directory identifiers), and add a targeted unit test for this case.

Verdict

CHANGES REQUESTED: The current watcher behavior can miss file content changes in watched directories and changes callback semantics, both of which risk regressions.


This is an automated AI review using gpt-5.2-codex-high. Please verify the feedback and use your judgment.

@github-actions github-actions bot added the do-not-merge PR is blocked from merging label Jan 21, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR updates config file watching to detect newly created config.toml files by watching the file path directly (with allow_nonexistent=True), clarifies watcher behavior in docstrings, and adds targeted unit tests for event-based/polling watchers and bootstrap configuration watchers. No frontend or protobuf changes.

Code Quality

The changes are small, well-scoped, and follow existing watcher patterns. Docstrings now clarify file vs directory watcher behavior and callback semantics. No structural concerns in the Python code.

Test Coverage

Python unit tests were added to cover the new allow_nonexistent behavior for both event-based and polling watchers, plus plumbing through watch_file and bootstrap config watchers. These tests include positive and negative checks and provide good coverage for the change. No e2e tests were added, which is appropriate given the backend-only scope.

Backwards Compatibility

Backwards compatible. watch_file adds a keyword-only optional parameter and preserves prior behavior by default. Existing call sites should remain unaffected.

Security & Risk

Low risk. The watcher changes are localized and do not affect security-sensitive paths. There is a minor operational edge case: if the parent .streamlit directory does not exist, watchdog scheduling can fail, so config creation might still not be detected in that scenario.

Recommendations

  1. Consider handling the case where the parent .streamlit directory is missing by either ensuring the directory exists or watching the parent directory for creation events. This would make “load config on creation” fully reliable on first-run setups. (lib/streamlit/web/bootstrap.py:323-330)
  2. New unit tests are missing type annotations, which the Python test guide recommends for new tests. Consider adding types to the new test methods. (lib/tests/streamlit/watcher/event_based_path_watcher_test.py:520-648, lib/tests/streamlit/watcher/polling_path_watcher_test.py:247-306)

Verdict

APPROVED: Solid, low-risk improvement with good unit test coverage; only minor follow-up recommendations.


This is an automated AI review using gpt-5.2-codex-high. Please verify the feedback and use your judgment.

@github-actions github-actions bot removed the do-not-merge PR is blocked from merging label Jan 21, 2026
@lukasmasuch
Copy link
Copy Markdown
Collaborator Author

I decided to do a bit more tweaking and enforce usage of the poll based watcher for config files. This should be better for specific paths and is equivalent to what we already do for secrets.toml

@lukasmasuch lukasmasuch merged commit 2615b7c into develop Jan 21, 2026
43 checks passed
@lukasmasuch lukasmasuch deleted the lukasmasuch/load-config-on-creation branch January 21, 2026 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rerun when config.toml is created

3 participants