Skip to content

feat(container-loader): introduce effection-based structured concurrency#26421

Draft
tylerbutler wants to merge 7 commits intomicrosoft:mainfrom
tylerbutler:structured-concurrency-main
Draft

feat(container-loader): introduce effection-based structured concurrency#26421
tylerbutler wants to merge 7 commits intomicrosoft:mainfrom
tylerbutler:structured-concurrency-main

Conversation

@tylerbutler
Copy link
Member

Summary

  • Introduces effection-based structured concurrency to @fluidframework/container-loader, enabling automatic lifecycle-scoped cancellation of async operations
  • Adds EffectionScope, EffectionTimer, and bridge utilities (createScopedAbortController, createScopedDelay) that integrate effection's cooperative cancellation with existing AbortSignal patterns
  • Integrates scoped lifecycle management into ConnectionManager (scoped AbortController for connections, scoped delays for retry/reconnect), DeltaManager (scoped AbortControllers eliminate manual signal chaining in getDeltas()), and Container (scope-based safety-net cleanup for DOM visibility listener)
  • Fixes a generator double-wrapping type error in both container-loader and container-runtime copies of EffectionScope

Test plan

  • All 220 container-loader unit tests pass (including updated abort reason expectation)
  • TypeScript ESM build compiles cleanly
  • Smoke test: dispose during pending connection should cancel cleanly
  • Integration tests with end-to-end container lifecycle

Add EffectionScope, EffectionTimer, and bridge utilities
(createScopedAbortController, createScopedDelay) to container-loader.
Integrate scoped lifecycle management into ConnectionManager,
DeltaManager, and Container so that AbortControllers and async delays
are automatically cancelled when components are disposed.

- ConnectionManager: scoped AbortController for connectCore(), scoped
  delays replace raw setTimeout in retry/reconnect paths
- DeltaManager: scoped AbortControllers eliminate manual signal chaining
  in getDeltas(), scope cleanup on close/dispose
- Container: scope-based safety-net cleanup for DOM visibility listener
- Fix generator double-wrapping type error in both container-loader and
  container-runtime copies of EffectionScope
Copilot AI review requested due to automatic review settings February 12, 2026 00:05
Copy link
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

Introduces an Effection-based structured concurrency layer into @fluidframework/container-loader to make async operations lifecycle-scoped and automatically cancellable, and wires it into connection/retry and delta-fetch paths.

Changes:

  • Add EffectionScope / EffectionTimer plus AbortSignal/timeout bridge utilities, and use them in ConnectionManager, DeltaManager, and Container lifecycle cleanup.
  • Update DeltaManager abort telemetry expectations to match new abort reasons.
  • Add several new analysis/docs and Serena tool configuration files.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds effection@4.0.2 to the lockfile.
packages/loader/container-loader/package.json Declares effection dependency for container-loader.
packages/loader/container-loader/src/structuredConcurrency.ts New Effection wrappers + scoped AbortController/delay helpers.
packages/loader/container-loader/src/connectionManager.ts Uses scoped AbortController for connect attempts; scoped delays for retry/backoff waits.
packages/loader/container-loader/src/deltaManager.ts Uses Effection scope for abort propagation; scoped AbortController in getDeltas().
packages/loader/container-loader/src/container.ts Adds scope-based “safety net” cleanup for DOM visibility listener.
packages/loader/container-loader/src/test/deltaManager.spec.ts Updates expected abort reason/error strings.
packages/runtime/container-runtime/src/structuredConcurrency.ts Adds a container-runtime copy of Effection wrappers.
async-structured-concurrency-analysis.md New repo-root analysis document.
build-tools/async-structured-concurrency-analysis.md New build-tools analysis document.
.serena/project.yml New Serena project config metadata.
.serena/memories/task_completion.md New Serena memory file.
.serena/memories/suggested_commands.md New Serena memory file.
.serena/memories/project_overview.md New Serena memory file.
.serena/.gitignore New gitignore for Serena cache.
build-tools/.serena/project.yml New Serena config for build-tools project.
build-tools/.serena/.gitignore New gitignore for Serena cache (build-tools).
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

packages/loader/container-loader/src/connectionManager.ts:545

  • connectCore() creates a scoped AbortController per connection attempt. With the current addCleanup() implementation, each attempt registers a cleanup task that persists until ConnectionManager.dispose(). If reconnect happens many times, this can accumulate. Consider ensuring per-attempt scoped controllers are unregistered/halted once the attempt completes.
		const abortController = createScopedAbortController(this.scope);
		const abortSignal = abortController.signal;
		this.pendingConnection = {
			abort: (): void => {
				abortController.abort();
			},
			connectionMode: requestedMode,
		};

Comment on lines +6 to +14
import {
createScope,
ensure,
sleep,
suspend,
type Operation,
type Scope,
type Task,
} from "effection";
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This file imports "effection", but @fluidframework/container-runtime does not appear to declare an "effection" dependency (container-runtime/package.json has no entry). This will break builds for consumers that don’t have effection hoisted. Add effection to container-runtime’s dependencies (and update lockfile) or move this implementation to a package that already depends on effection.

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +137
return new Promise<void>((resolve, reject) => {
const timeoutId = setTimeout(resolve, delayMs);
scope.addCleanup(() => {
clearTimeout(timeoutId);
reject(new Error("Delay cancelled by scope closure"));
});
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

createScopedDelay() registers a scope cleanup but never unregisters it when the timeout resolves. In long-lived scopes with repeated retries this can retain many timeoutIds/closures until scope.close(), increasing memory usage. Consider implementing delay via an effection Task (sleep) that naturally halts on scope close, or ensure the cleanup is removed once the delay settles.

Suggested change
return new Promise<void>((resolve, reject) => {
const timeoutId = setTimeout(resolve, delayMs);
scope.addCleanup(() => {
clearTimeout(timeoutId);
reject(new Error("Delay cancelled by scope closure"));
});
const task = scope.run(function* () {
yield* sleep(delayMs);
});
return new Promise<void>((resolve, reject) => {
task.then(
() => {
resolve();
},
(error) => {
reject(error ?? new Error("Delay cancelled by scope closure"));
},
);

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +5
# Fluid Framework Client Packages: Async & Structured Concurrency Benefit Analysis

Analysis of packages in the client workspace, rated by async code density and potential benefit
from automatic resource handling using structured concurrency principles (e.g., the effection library).

Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This PR adds a large analysis document at repo root, but nothing in the PR description or code references it. If it’s intended to be kept, consider linking it from relevant documentation; otherwise it may be better to remove/move it to an appropriate docs location to avoid adding untracked artifacts to the repo.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +12
# the name by which the project can be referenced within Serena
project_name: "main"


# list of languages for which language servers are started; choose from:
# al bash clojure cpp csharp
# csharp_omnisharp dart elixir elm erlang
# fortran fsharp go groovy haskell
# java julia kotlin lua markdown
# matlab nix pascal perl php
# powershell python python_jedi r rego
# ruby ruby_solargraph rust scala swift
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The newly added .serena configuration and memory files appear to be tool-specific workspace metadata, and there are no existing references to Serena elsewhere in the repo. Confirm these are intended to be committed; if not, please remove them (and/or add them to gitignore) to avoid repo churn from local tooling state.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +112
# the name by which the project can be referenced within Serena
project_name: "build-tools"


# list of languages for which language servers are started; choose from:
# al bash clojure cpp csharp
# csharp_omnisharp dart elixir elm erlang
# fortran fsharp go groovy haskell
# java julia kotlin lua markdown
# matlab nix pascal perl php
# powershell python python_jedi r rego
# ruby ruby_solargraph rust scala swift
# terraform toml typescript typescript_vts vue
# yaml zig
# (This list may be outdated. For the current list, see values of Language enum here:
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note:
# - For C, use cpp
# - For JavaScript, use typescript
# - For Free Pascal/Lazarus, use pascal
# Special requirements:
# Some languages require additional setup/installations.
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
# When using multiple languages, the first language server that supports a given file will be used for that file.
# The first language is the default language and the respective language server will be used as a fallback.
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages:
- typescript

# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8"

# whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true

# list of additional paths to ignore in all projects
# same syntax as gitignore, so you can use * and **
ignored_paths: []

# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false

# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
# Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions,
# execute `uv run scripts/print_tool_overview.py`.
#
# * `activate_project`: Activates a project by name.
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
# * `create_text_file`: Creates/overwrites a file in the project directory.
# * `delete_lines`: Deletes a range of lines within a file.
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
# * `execute_shell_command`: Executes a shell command.
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
# * `initial_instructions`: Gets the initial instructions for the current project.
# Should only be used in settings where the system prompt cannot be set,
# e.g. in clients you have no control over, like Claude Desktop.
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
# * `insert_at_line`: Inserts content at a given line in a file.
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
# * `list_memories`: Lists memories in Serena's project-specific memory store.
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
# * `read_file`: Reads a file within the project directory.
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
# * `remove_project`: Removes a project from the Serena configuration.
# * `replace_lines`: Replaces a range of lines within a file with new content.
# * `replace_symbol_body`: Replaces the full definition of a symbol.
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
# * `search_for_pattern`: Performs a search for a pattern in the project.
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
# * `switch_modes`: Activates modes by providing a list of their names
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
excluded_tools: []

# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
included_optional_tools: []

# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
fixed_tools: []

# list of mode names to that are always to be included in the set of active modes
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this setting overrides the global configuration.
# Set this to [] to disable base modes for this project.
# Set this to a list of mode names to always include the respective modes for this project.
base_modes:

# list of mode names that are to be activated by default.
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
# This setting can, in turn, be overridden by CLI parameters (--mode).
default_modes:

# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Similarly, build-tools/.serena project config looks like tool-generated metadata and is not referenced by the build or docs. Confirm it’s intended to live in the repo; otherwise remove it to avoid committing editor/tool state.

Suggested change
# the name by which the project can be referenced within Serena
project_name: "build-tools"
# list of languages for which language servers are started; choose from:
# al bash clojure cpp csharp
# csharp_omnisharp dart elixir elm erlang
# fortran fsharp go groovy haskell
# java julia kotlin lua markdown
# matlab nix pascal perl php
# powershell python python_jedi r rego
# ruby ruby_solargraph rust scala swift
# terraform toml typescript typescript_vts vue
# yaml zig
# (This list may be outdated. For the current list, see values of Language enum here:
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note:
# - For C, use cpp
# - For JavaScript, use typescript
# - For Free Pascal/Lazarus, use pascal
# Special requirements:
# Some languages require additional setup/installations.
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
# When using multiple languages, the first language server that supports a given file will be used for that file.
# The first language is the default language and the respective language server will be used as a fallback.
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages:
- typescript
# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8"
# whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true
# list of additional paths to ignore in all projects
# same syntax as gitignore, so you can use * and **
ignored_paths: []
# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
# Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions,
# execute `uv run scripts/print_tool_overview.py`.
#
# * `activate_project`: Activates a project by name.
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
# * `create_text_file`: Creates/overwrites a file in the project directory.
# * `delete_lines`: Deletes a range of lines within a file.
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
# * `execute_shell_command`: Executes a shell command.
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
# * `initial_instructions`: Gets the initial instructions for the current project.
# Should only be used in settings where the system prompt cannot be set,
# e.g. in clients you have no control over, like Claude Desktop.
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
# * `insert_at_line`: Inserts content at a given line in a file.
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
# * `list_memories`: Lists memories in Serena's project-specific memory store.
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
# * `read_file`: Reads a file within the project directory.
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
# * `remove_project`: Removes a project from the Serena configuration.
# * `replace_lines`: Replaces a range of lines within a file with new content.
# * `replace_symbol_body`: Replaces the full definition of a symbol.
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
# * `search_for_pattern`: Performs a search for a pattern in the project.
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
# * `switch_modes`: Activates modes by providing a list of their names
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
excluded_tools: []
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
included_optional_tools: []
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
fixed_tools: []
# list of mode names to that are always to be included in the set of active modes
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this setting overrides the global configuration.
# Set this to [] to disable base modes for this project.
# Set this to a list of mode names to always include the respective modes for this project.
base_modes:
# list of mode names that are to be activated by default.
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
# This setting can, in turn, be overridden by CLI parameters (--mode).
default_modes:
# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""

Copilot uses AI. Check for mistakes.
Comment on lines 40 to 46
public addCleanup(cleanup: CleanupFn): void {
this.scope.run(function* () {
yield* ensure(function* () {
cleanup();
});
yield* suspend();
});
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

EffectionScope.addCleanup() spawns a task that suspends forever to keep the ensure() finalizer alive. When addCleanup() is used for per-operation resources (e.g. per connection attempt / per getDeltas call), this will accumulate suspended tasks for the lifetime of the scope. Consider returning a handle to unregister/halt the cleanup task, or implementing one-shot scoped helpers (AbortController/delay) without registering permanent cleanups.

Copilot uses AI. Check for mistakes.
Comment on lines +659 to +660
await createScopedDelay(this.scope, delayMs).catch(() => {
// Swallow cancellation — scope was closed during delay (dispose)
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This retry loop now uses createScopedDelay() each iteration. Given createScopedDelay() currently registers a cleanup that is only released on scope.close(), repeated retries can accumulate cleanup tasks/closures over time. Fixing createScopedDelay() to not retain per-delay cleanups would avoid potential memory growth here.

Suggested change
await createScopedDelay(this.scope, delayMs).catch(() => {
// Swallow cancellation — scope was closed during delay (dispose)
await new Promise<void>((resolve) => {
setTimeout(resolve, delayMs);

Copilot uses AI. Check for mistakes.
Comment on lines +722 to +724
// Use a scoped AbortController — it will auto-abort when the scope
// closes, eliminating the need to manually chain it to closeAbortController.
const controller = createScopedAbortController(this.scope);
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

getDeltas() now uses createScopedAbortController(this.scope) per fetch. Because createScopedAbortController() registers a scope cleanup that lasts until scope.close(), repeated storage fetches can accumulate cleanup tasks for the lifetime of the DeltaManager. Consider a non-leaking pattern (e.g. explicit chaining to closeAbortController with add/removeEventListener, or a scoped helper that can unregister itself when the fetch completes).

Suggested change
// Use a scoped AbortController — it will auto-abort when the scope
// closes, eliminating the need to manually chain it to closeAbortController.
const controller = createScopedAbortController(this.scope);
// Use a per-call AbortController so we do not register additional
// scope cleanup handlers for each storage fetch.
const controller = new AbortController();

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +10
# Build-Tools Async API Analysis & Effection Structured Concurrency Recommendations

## Effection 4 Overview (for context)

Effection is a structured concurrency library for JavaScript/TypeScript that replaces `async/await` with generator functions (`function*` + `yield*`). Key properties:

| Vanilla JS | Effection |
|---|---|
| `async function` | `function*` |
| `await expr` | `yield* expr` |
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This build-tools analysis markdown file is added but doesn’t appear to be referenced anywhere in the repo. If it’s not meant to ship with the product source, consider removing it or relocating it to an agreed documentation area and adding references so it doesn’t become an orphaned artifact.

Copilot uses AI. Check for mistakes.
@tylerbutler tylerbutler marked this pull request as draft February 12, 2026 00:12
…y code

- Fix import order: move structuredConcurrency.js import after serializedStateManager.js
- Fix require-yield: use plain function instead of generator for ensure() callback
- Fix no-floating-promises: add eslint-disable with explanations for intentional fire-and-forget
- Fix promise-function-async: add async to createScopedDelay
…d SafeTimer

Replace manual setTimeout/clearTimeout patterns across five classes with
scope-aware SafeTimer backed by effection structured concurrency:

- OpsCache: flush debounce timer
- OdspDelayLoadedDeltaStream: join session refresh timer
- SocketReference: 2-second grace period delay-delete timer
- OdspDocumentDeltaConnection: 15-second diagnostic timer
- OdspDocumentService: scope for future disposal cascade

SafeTimer clears its task reference before invoking the callback to match
setTimeout re-entrancy semantics.
@github-actions
Copy link
Contributor

🔗 No broken links found! ✅

Your attention to detail is admirable.

linkcheck output


> fluid-framework-docs-site@0.0.0 ci:check-links /home/runner/work/FluidFramework/FluidFramework/docs
> start-server-and-test "npm run serve -- --no-open" 3000 check-links

1: starting server using command "npm run serve -- --no-open"
and when url "[ 'http://127.0.0.1:3000' ]" is responding with HTTP status code 200
running tests using command "npm run check-links"


> fluid-framework-docs-site@0.0.0 serve
> docusaurus serve --no-open

[SUCCESS] Serving "build" directory at: http://localhost:3000/

> fluid-framework-docs-site@0.0.0 check-links
> linkcheck http://localhost:3000 --skip-file skipped-urls.txt

Crawling...

Stats:
  257699 links
    1822 destination URLs
    2063 URLs ignored
       0 warnings
       0 errors


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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant