Skip to content

feat: add --no-reload flag to disable hot reload for local commands - #56

Closed
cortex-assistant[bot] wants to merge 1 commit into
developfrom
feat/no-reload-flag
Closed

feat: add --no-reload flag to disable hot reload for local commands#56
cortex-assistant[bot] wants to merge 1 commit into
developfrom
feat/no-reload-flag

Conversation

@cortex-assistant

Copy link
Copy Markdown

Fixes aws#8782.

Summary

Adds a --no-reload flag to sam local start-api and sam local start-lambda commands that disables file watching and hot reload functionality. This addresses severe performance issues on Windows where Microsoft Defender causes multi-minute delays during recursive directory observation.

Changes

  • Added --no-reload CLI option to warm_containers_common_options in options.py
  • Wired flag through start_api/cli.py and start_lambda/cli.py
  • Propagated flag through InvokeContext to WarmLambdaRuntime and RefreshableSamFunctionProvider
  • WarmLambdaRuntime skips observer.watch() and observer.start() when --no-reload is set
  • RefreshableSamFunctionProvider skips template file watching when --no-reload is set
  • Added unit tests for both components

Testing

  • Unit tests added for WarmLambdaRuntime (skips observer when --no-reload) and RefreshableSamFunctionProvider (skips template watch when --no-reload)
  • When --no-reload is used with --warm-containers, containers still work but code changes are not automatically detected

Fixes aws#8782.

Adds a --no-reload flag to sam local start-api and sam local start-lambda
commands that disables file watching and hot reload functionality. This
addresses severe performance issues on Windows where Microsoft Defender
causes multi-minute delays during recursive directory observation.

Changes:
- Added --no-reload CLI option to warm_containers_common_options in options.py
- Wired flag through start_api/cli.py and start_lambda/cli.py
- Propagated flag through InvokeContext to WarmLambdaRuntime and RefreshableSamFunctionProvider
- WarmLambdaRuntime skips observer.watch/start when --no-reload is set
- RefreshableSamFunctionProvider skips template file watching when --no-reload is set
- Added unit tests for both components

@cortex-assistant cortex-assistant Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

PR Review: feat: add --no-reload flag to disable hot reload for local commands

Good feature that addresses a real pain point (Windows Defender causing multi-minute delays with file watchers). The implementation is clean and well-scoped overall. I have a few issues to address before this should be merged.

✅ What looks good

  • CLI option placement — Adding --no-reload to warm_containers_common_options is the right grouping since hot reload only applies to warm containers.
  • Conditional guard in invoke_context.py — Only passing no_reload to the function provider when ContainersMode.WARM is correct and avoids leaking the flag to cold mode.
  • Debug logging — Good use of LOG.debug() messages so users can diagnose behavior.
  • Unit tests — Both positive and negative cases are covered for WarmLambdaRuntime and RefreshableSamFunctionProvider.

🔴 Issues to address

See inline comments for details.

self._observer = FileObserver(self._set_templates_changed)
self._observer.start()
self._watch_stack_templates(stacks)
if no_reload:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Bug: Observer is still started even when no_reload=True

The FileObserver is unconditionally created and started on the lines above:

self._observer = FileObserver(self._set_templates_changed)
self._observer.start()

But the no_reload guard only skips _watch_stack_templates(). This means the observer thread is still spawned and running (consuming resources) even though nothing will ever be watched. This partially defeats the purpose of the flag — on Windows, even an idle FileObserver thread may interact with the file system.

You should also skip self._observer.start() when no_reload=True, or better yet, skip creating the observer entirely:

if no_reload:
    LOG.debug("Hot reload is disabled (--no-reload). Skipping template file observer.")
    self._observer = None
else:
    self._observer = FileObserver(self._set_templates_changed)
    self._observer.start()
    self._watch_stack_templates(stacks)

⚠️ If you go this route, you'll need to guard self._observer.stop() in the stop() method as well (check for None).

"""

def __init__(self, container_manager, image_builder, observer=None, mount_symlinks=False, no_mem_limit=False):
def __init__(self, container_manager, image_builder, observer=None, mount_symlinks=False, no_mem_limit=False, no_reload=False):

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Nit: Line length exceeds 120 characters

This line is quite long. Consider wrapping the parameters:

def __init__(
    self, container_manager, image_builder, observer=None,
    mount_symlinks=False, no_mem_limit=False, no_reload=False,
):

@@ -256,7 +258,6 @@ def __enter__(self) -> "InvokeContext":
ContainersMode.WARM: [self._stacks, self._parameter_overrides, self._global_parameter_overrides],
ContainersMode.COLD: [self._stacks],
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Nit: Unrelated whitespace change

This removes a blank line that was separating two logical blocks. It's a minor formatting change but unrelated to the feature — I'd restore the original blank line to avoid noise in the diff.

),
click.option(
"--no-reload",
is_flag=True,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Consider: Should --no-reload warn or error when used without --warm-containers?

Since --no-reload is only meaningful with warm containers, using it with cold containers silently does nothing. This could be confusing to users. Consider either:

  1. Emitting a warning when --no-reload is passed without --warm-containers, or
  2. Documenting in the help text that it requires --warm-containers.

At minimum, updating the help text to mention the dependency:

"Disable automatic reloading of Lambda functions when source code or templates change. "
"Only applies when used with --warm-containers. "
"Useful on Windows where file watchers can cause significant latency with security software."

runtime.create(func_config)

self.observer_mock.watch.assert_called_once_with(func_config)
self.observer_mock.start.assert_called_once()

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Suggestion: Assert the container is still created successfully when no_reload=True

The tests verify observer behavior but don't assert that the container itself was still created and returned. Adding assertions like:

result = runtime.create(func_config)
self.manager_mock.create.assert_called_once()
self.assertIsNotNone(result)

would strengthen the test by verifying that skipping the observer doesn't break container creation.

@bnusunny bnusunny closed this Mar 20, 2026

@cortex-assistant cortex-assistant Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

PR Review: feat: add --no-reload flag to disable hot reload for local commands

Overall this is a well-structured change that cleanly threads a new --no-reload flag through the CLI → InvokeContext → runtime/provider pipeline. The motivation (Windows Defender latency on file watchers) is valid and the approach is sound. However, I have a few concerns that should be addressed before merging.

✅ What looks good

  • Clean plumbing: The flag is properly wired through options.pycli.pyinvoke_context.pyWarmLambdaRuntime / RefreshableSamFunctionProvider.
  • Good test coverage: Both positive and negative test cases for WarmLambdaRuntime and RefreshableSamFunctionProvider.
  • Appropriate scoping: The flag is only propagated to WarmLambdaRuntime (not LambdaRuntime) and to RefreshableSamFunctionProvider (not SamFunctionProvider), which is correct since only warm containers use file watching.
  • Debug logging: Helpful debug messages when the flag is active.

🔴 Issues to address

  1. RefreshableSamFunctionProvider still starts the observer even when no_reload=True — The FileObserver is unconditionally created and started (self._observer.start()), but only the _watch_stack_templates call is skipped. This means a background observer thread is still running doing nothing. If the goal is to avoid file-watcher overhead, the observer should also be skipped entirely.

  2. --no-reload without --warm-containers is silently ignored — The flag is defined in warm_containers_common_options and only takes effect in warm container mode, but there's no user-facing warning if someone passes --no-reload without --warm-containers. This could be confusing. Consider either logging a warning or validating the combination.

  3. Line length violation — The __init__ signature in runtime.py exceeds typical line length limits after adding the new parameter.

  4. Unrelated whitespace change — A blank line was removed in invoke_context.py which is unrelated to the feature.

  5. Missing __init__ docstring update — The no_reload parameter isn't documented in the docstrings of WarmLambdaRuntime.__init__, RefreshableSamFunctionProvider.__init__, or InvokeContext.__init__.

self._observer = FileObserver(self._set_templates_changed)
self._observer.start()
self._watch_stack_templates(stacks)
if no_reload:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🔴 Observer still started when no_reload=True: The FileObserver is created and self._observer.start() is called unconditionally on line 936, even when no_reload=True. This means a background thread is still running even though nothing is being watched.

Consider wrapping both the observer creation and start in the condition:

if no_reload:
    LOG.debug("Hot reload is disabled (--no-reload). Skipping template file observer.")
    self._observer = None
else:
    self._observer = FileObserver(self._set_templates_changed)
    self._observer.start()
    self._watch_stack_templates(stacks)

Note: you'd also need to guard self._observer.stop() in the stop() method with an if self._observer: check.

"""

def __init__(self, container_manager, image_builder, observer=None, mount_symlinks=False, no_mem_limit=False):
def __init__(self, container_manager, image_builder, observer=None, mount_symlinks=False, no_mem_limit=False, no_reload=False):

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🟡 Line too long: This line is ~113 characters after adding no_reload=False. Consider wrapping the parameters:

def __init__(
    self, container_manager, image_builder, observer=None,
    mount_symlinks=False, no_mem_limit=False, no_reload=False
):

@@ -256,7 +258,6 @@ def __enter__(self) -> "InvokeContext":
ContainersMode.WARM: [self._stacks, self._parameter_overrides, self._global_parameter_overrides],
ContainersMode.COLD: [self._stacks],
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🟡 Unrelated whitespace change: This removes a blank line that was separating the dict definitions from the following comment block. Minor, but it's unrelated to the feature and adds noise to the diff. Consider reverting.

@@ -267,6 +268,9 @@ def __enter__(self) -> "InvokeContext":
if self._function_logical_ids:
_function_providers_kwargs["function_logical_ids"] = self._function_logical_ids

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🟡 Consider warning when --no-reload is used without --warm-containers: Currently no_reload is only passed to the function provider when containers_mode == WARM. If a user passes --no-reload without --warm-containers, it's silently ignored. A LOG.warning() (or a click validation callback) would improve the UX:

if self._no_reload and self._containers_mode != ContainersMode.WARM:
    LOG.warning("--no-reload has no effect without --warm-containers")

),
click.option(
"--no-reload",
is_flag=True,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🟢 Nice help text — clearly explains the use case. Consider also mentioning that this flag only takes effect with --warm-containers so users know upfront:

"Disable automatic reloading of Lambda functions when source code or templates change. "
"Only effective with --warm-containers. "
"Useful on Windows where file watchers can cause significant latency with security software."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: SAM Local Flag to Disable Hot Reload

1 participant