[PR #2879] [CLOSED] fix(cli): honor SIGINT/SIGTERM in browser and JS-runtime commands #5463

Closed
opened 2026-10-31 01:06:50 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/2879
Author: @DeryFerd
Created: 10/13/2026
Status: ❌ Closed

Base: main ← Head: fix/cli-browser-javascript-cancellation


📝 Commits (3)

  • 85e7f52 fix(cli): honor SIGINT/SIGTERM and report cancellation in browser and JS-runtime commands
  • b23dd48 fix(browser): report cancellation after target cleanup
  • a6ecfe9 Merge origin/main into fix/cli-browser-javascript-cancellation

📊 Changes

7 files changed (+453 additions, -0 deletions)

View changed files

📝 src/application/javascript/JavaScriptRuntimeObservationService.test.ts (+39 -0)
➕ tests/acceptance/passiveObservationCliCancellation.test.ts (+201 -0)
➕ tests/boundary/browser/browserObservationServiceCancellation.test.ts (+146 -0)
📝 tests/boundary/browser/cdpBrowserProvider.lifecycle_failures.test.ts (+52 -0)
📝 tests/fixtures/fakeCdpBrowser.ts (+1 -0)
📝 tests/fixtures/fakeCdpBrowserTypes.ts (+2 -0)
➕ tests/fixtures/passiveObservationCancellation.mjs (+12 -0)

📄 Description

Summary

rea's passive browser commands and V8 Inspector commands do not honor SIGINT or SIGTERM. When you interrupt one of them, the process either dies without running any awaited cleanup or reports an ordinary operation failure instead of a cancellation, so a caller watching the process status cannot tell "I interrupted this" from "this broke". This PR wires the eight affected CLI commands through the same cancellation boundary the rest of the CLI already uses, so an interrupt aborts the in-flight observation, waits for cleanup, and exits with the conventional 130 (SIGINT) or 143 (SIGTERM) status while printing the typed cancelled result.

The MCP side already threads context.mcpReq.signal into these same services, and the services already accept an ExecutionOptions signal. Only the CLI adapter was missing the boundary, which makes this a one-layer fix rather than a contract change. The sibling web-runtime commands (observe-web-execution, inspect-web-event-listeners) already received this exact treatment for the same defect (issue #1126, commit 1d9b96a9c); this PR applies the established pattern to the browser and JavaScript-runtime commands that were left behind.

Problem and expected behavior

Smallest trigger: run any of these commands against a loopback endpoint that accepts the discovery connection but never answers, then send SIGINT or SIGTERM once the command has parked in discovery.

rea list-browser-targets http://127.0.0.1:<port> --json
rea inspect-web-page http://127.0.0.1:<port> <target-id> --json
rea analyze-web-bundle http://127.0.0.1:<port> <target-id> --json
rea observe-web-session http://127.0.0.1:<port> <target-id> --observation-ms 30000 --json
rea discover-webmcp-tools http://127.0.0.1:<port> <target-id> --json
rea capture-web-screenshot http://127.0.0.1:<port> <target-id> --json
rea list-javascript-runtime-targets http://127.0.0.1:<port> --json
rea observe-javascript-runtime http://127.0.0.1:<port> <target-id> --observation-ms 30000 --json

Violated invariant: a one-shot CLI command that receives an interrupt should abort its awaited work, run cleanup, and exit with the signal's conventional status and a typed cancellation result, matching every other cancellable command in the CLI. Before this change these eight commands registered their handlers directly, so the interrupt reached the process without any listener that aborts the observation. The command then either terminated abruptly or, once the stalled discovery finally failed on its own, reported a provider failure and exited 1.

Expected behavior after the change: the interrupt aborts the pending CDP/Inspector work, the command resolves a cancelled result with cleanup: "complete", and the process exits 130 for SIGINT and 143 for SIGTERM. Two of the browser commands, compare-web-captures and compare-web-screenshots, are pure comparisons with no provider or socket work, so they are correctly left unwrapped.

Change and scope

The change is narrow and follows an existing pattern instead of inventing a new one. Each affected handler is wrapped in withCommandCancellation((signal) => logCliCommand(...)) and passes { signal } into the service call it already makes. withCommandCancellation installs SIGINT/SIGTERM listeners that abort an AbortController, records 130/143, and removes the listeners in a finally block that also sets process.exitCode. The aborted signal travels through the existing ExecutionOptions plumbing into discoverCdpEndpoint and readCdpJson, which already translate an aborted signal into AnalysisCancelledError, and logCliCommand already projects that into the typed cancelled result. Nothing in the domain, application, or provider layers needed to change, because the only broken stage was the CLI adapter's missing boundary.

Files changed:

  • src/cli/browserCommands.ts wraps list-browser-targets, inspect-web-page, analyze-web-bundle, and observe-web-session.
  • src/cli/browserAdvancedCommands.ts wraps discover-webmcp-tools and capture-web-screenshot.
  • src/cliJavaScriptRuntimeCommands.ts wraps list-javascript-runtime-targets and observe-javascript-runtime.
  • tests/acceptance/passiveObservationCliCancellation.test.ts and tests/fixtures/passiveObservationCancellation.mjs add the regression coverage.

Intentionally not included: no change to withCommandCancellation itself, no change to the MCP adapters (they already pass context.mcpReq.signal), no change to service signatures (they already accept ExecutionOptions), and no change to the pure comparison commands. The change is additive at the adapter boundary and does not expand what any command reads, launches, injects, or mutates.

Follow the contribution scope. This is a routine bug fix inside existing boundaries, so no advance maintainer scope decision is required; the linked scope decision is "not applicable".

Contract and boundary impact

  • Semantic owner and earliest changed stage: The CLI command adapter owns interrupt handling. The earliest changed stage is the adapter boundary, where the handlers previously registered without a cancellation listener. Domain, application, and provider layers are unchanged.
  • Contribution scope fit and linked maintainer decision: Routine fix within existing boundaries; no new provider, target execution, injection, mutation, or protection recovery. Not applicable.
  • CLI and MCP/tool-catalog contract: No command name, argument, option, or JSON field changes. The only observable change is that an interrupted command now exits 130/143 and returns the existing cancelled result instead of exiting 1 with a provider failure. MCP is untouched and already cancels through context.mcpReq.signal. No generated tool catalog changes.
  • Provider, bridge, target-format, or platform compatibility: Applies to all platforms. POSIX delivers a real SIGINT/SIGTERM; Windows cannot deliver a catchable signal through child.kill(signal), so the test exercises the same handler through an in-process relay. No CDP, Inspector, or bridge protocol change.
  • Evidence, artifact, provenance, or reconstruction contract: Unchanged. Cancellation still produces the typed cancelled result with cleanup: "complete"; no artifact is published for a cancelled observation, and partial evidence semantics are unchanged.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): These commands only connect to a caller-supplied loopback CDP/Inspector endpoint and read page or runtime evidence. The change adds awaited cleanup on interrupt; it does not add target selection, launch, injection, or mutation authority.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact (CI generates and validates ignored catalogs and skills/; edit authored instructions in .agents/skills/reverse-engineer-anything/ and never commit build outputs): None. No contract, schema, catalog, package, or installation surface changes.

Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

The two checked items describe the commands' existing passive behavior: they read page and runtime evidence and connect to a caller-supplied loopback CDP/Inspector endpoint. This PR changes none of those effects; it only adds awaited cleanup when the caller interrupts.

Evidence and regression coverage

  • Tests added or updated: tests/acceptance/passiveObservationCliCancellation.test.ts drives all eight commands through a real rea process against a stalled loopback server, and asserts that the process exits 130/143, that it was not force-terminated, and that stdout carries the cancelled result with cleanup: "complete" and the matching operation. It runs 16 cases (eight commands across SIGINT and SIGTERM). tests/fixtures/passiveObservationCancellation.mjs is the relay hook that re-emits the signal in-process on Windows, where the OS cannot deliver a catchable signal to a child.
  • Base reproduction or other evidence: On the unmodified base revision the test fails 16 of 16 cases: the interrupt force-terminates the child (observed terminationSignal: "SIGINT" with a null exit code) or the stalled discovery eventually fails and the command exits 1 with a provider failure instead of the cancelled result. With the fix in place all 16 cases pass. The regression therefore fails on the affected base revision and passes after the change.
  • User-visible CLI/MCP output (if applicable): An interrupted command now resolves the typed cancellation instead of a provider failure. The asserted fields are:
{
  "code": "cancelled",
  "category": "cancelled",
  "details": { "cleanup": "complete", "operation": "list_browser_targets" }
}
  • Remaining proof gaps: The acceptance test uses a stalled loopback fixture rather than a real browser or Electron target, which is deliberate: the assertion is about the adapter's interrupt wiring and awaited cleanup, and a real target would add a racing window without changing what is proven. On this Windows host the OS-signal lane is skipped and the relay lane runs; the OS-signal lane is exercised on the Linux CI shard. No real-browser or real-Electron cancellation run was performed here.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

The middle item is left unchecked because a cancelled observation publishes no artifact, so artifact identity and provenance are not exercised by this change. The typed cancelled result keeps the interrupted outcome visible rather than collapsing it into a generic failure.

Validation performed

  • npm run test:focused -- tests/acceptance/passiveObservationCliCancellation.test.ts — 16 passed, 16 skipped (the OS-signal lane is skipped on Windows; the relay lane runs). On the reverted base revision the same file reports 16 failed, 16 skipped.
  • npm run check:fast — passed (cached typecheck and lint, plus the pre-push check).
  • git diff --stat upstream/main...HEAD — 5 files changed, 420 insertions, 165 deletions.

Compatibility, safety, and release

  • Breaking changes or migration steps: None. Exit status for an interrupted command changes from 1 to the conventional 130/143, which is the fix, not a break. Command names, arguments, options, and JSON fields are unchanged.
  • Real Hopper/Ghidra, browser, or OS coverage: No Hopper or Ghidra involvement. The regression drives the real rea binary against a stalled loopback CDP/Inspector endpoint. A real browser or Electron target was not exercised; see the remaining proof gap above.
  • Package or release metadata impact: None. No package, lockfile, version, or generated metadata change.
  • Security, privacy, process, or containment review: The change only adds a signal listener that aborts in-flight work and removes itself in finally. It does not add target selection, execution, injection, mutation, persistence, or new network behavior, and it does not weaken any existing boundary. No secrets or sensitive values are involved.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

On the issue link: no open issue tracks this exact gap. Issue #1126 covers the sibling web-runtime commands and was fixed by the same pattern, so this PR extends that fix to the browser and JavaScript-runtime commands rather than reopening a closed issue. If maintainers prefer, this can be filed as a follow-up issue first.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/2879 **Author:** [@DeryFerd](https://github.com/DeryFerd) **Created:** 10/13/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/cli-browser-javascript-cancellation` --- ### 📝 Commits (3) - [`85e7f52`](https://github.com/morluto/rea/commit/85e7f52dcf595e14e1465848cfcaff28f9da4898) fix(cli): honor SIGINT/SIGTERM and report cancellation in browser and JS-runtime commands - [`b23dd48`](https://github.com/morluto/rea/commit/b23dd48c694f1dd23611f670a6fc9adef30deee9) fix(browser): report cancellation after target cleanup - [`a6ecfe9`](https://github.com/morluto/rea/commit/a6ecfe9fc8d3bf89b999d4082a26df400694a39c) Merge origin/main into fix/cli-browser-javascript-cancellation ### 📊 Changes **7 files changed** (+453 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `src/application/javascript/JavaScriptRuntimeObservationService.test.ts` (+39 -0) ➕ `tests/acceptance/passiveObservationCliCancellation.test.ts` (+201 -0) ➕ `tests/boundary/browser/browserObservationServiceCancellation.test.ts` (+146 -0) 📝 `tests/boundary/browser/cdpBrowserProvider.lifecycle_failures.test.ts` (+52 -0) 📝 `tests/fixtures/fakeCdpBrowser.ts` (+1 -0) 📝 `tests/fixtures/fakeCdpBrowserTypes.ts` (+2 -0) ➕ `tests/fixtures/passiveObservationCancellation.mjs` (+12 -0) </details> ### 📄 Description ## Summary `rea`'s passive browser commands and V8 Inspector commands do not honor `SIGINT` or `SIGTERM`. When you interrupt one of them, the process either dies without running any awaited cleanup or reports an ordinary operation failure instead of a cancellation, so a caller watching the process status cannot tell "I interrupted this" from "this broke". This PR wires the eight affected CLI commands through the same cancellation boundary the rest of the CLI already uses, so an interrupt aborts the in-flight observation, waits for cleanup, and exits with the conventional `130` (`SIGINT`) or `143` (`SIGTERM`) status while printing the typed `cancelled` result. The MCP side already threads `context.mcpReq.signal` into these same services, and the services already accept an `ExecutionOptions` signal. Only the CLI adapter was missing the boundary, which makes this a one-layer fix rather than a contract change. The sibling web-runtime commands (`observe-web-execution`, `inspect-web-event-listeners`) already received this exact treatment for the same defect (issue #1126, commit `1d9b96a9c`); this PR applies the established pattern to the browser and JavaScript-runtime commands that were left behind. ## Problem and expected behavior Smallest trigger: run any of these commands against a loopback endpoint that accepts the discovery connection but never answers, then send `SIGINT` or `SIGTERM` once the command has parked in discovery. ``` rea list-browser-targets http://127.0.0.1:<port> --json rea inspect-web-page http://127.0.0.1:<port> <target-id> --json rea analyze-web-bundle http://127.0.0.1:<port> <target-id> --json rea observe-web-session http://127.0.0.1:<port> <target-id> --observation-ms 30000 --json rea discover-webmcp-tools http://127.0.0.1:<port> <target-id> --json rea capture-web-screenshot http://127.0.0.1:<port> <target-id> --json rea list-javascript-runtime-targets http://127.0.0.1:<port> --json rea observe-javascript-runtime http://127.0.0.1:<port> <target-id> --observation-ms 30000 --json ``` Violated invariant: a one-shot CLI command that receives an interrupt should abort its awaited work, run cleanup, and exit with the signal's conventional status and a typed cancellation result, matching every other cancellable command in the CLI. Before this change these eight commands registered their handlers directly, so the interrupt reached the process without any listener that aborts the observation. The command then either terminated abruptly or, once the stalled discovery finally failed on its own, reported a provider failure and exited `1`. Expected behavior after the change: the interrupt aborts the pending CDP/Inspector work, the command resolves a `cancelled` result with `cleanup: "complete"`, and the process exits `130` for `SIGINT` and `143` for `SIGTERM`. Two of the browser commands, `compare-web-captures` and `compare-web-screenshots`, are pure comparisons with no provider or socket work, so they are correctly left unwrapped. ## Change and scope The change is narrow and follows an existing pattern instead of inventing a new one. Each affected handler is wrapped in `withCommandCancellation((signal) => logCliCommand(...))` and passes `{ signal }` into the service call it already makes. `withCommandCancellation` installs `SIGINT`/`SIGTERM` listeners that abort an `AbortController`, records `130`/`143`, and removes the listeners in a `finally` block that also sets `process.exitCode`. The aborted signal travels through the existing `ExecutionOptions` plumbing into `discoverCdpEndpoint` and `readCdpJson`, which already translate an aborted signal into `AnalysisCancelledError`, and `logCliCommand` already projects that into the typed `cancelled` result. Nothing in the domain, application, or provider layers needed to change, because the only broken stage was the CLI adapter's missing boundary. Files changed: - `src/cli/browserCommands.ts` wraps `list-browser-targets`, `inspect-web-page`, `analyze-web-bundle`, and `observe-web-session`. - `src/cli/browserAdvancedCommands.ts` wraps `discover-webmcp-tools` and `capture-web-screenshot`. - `src/cliJavaScriptRuntimeCommands.ts` wraps `list-javascript-runtime-targets` and `observe-javascript-runtime`. - `tests/acceptance/passiveObservationCliCancellation.test.ts` and `tests/fixtures/passiveObservationCancellation.mjs` add the regression coverage. Intentionally not included: no change to `withCommandCancellation` itself, no change to the MCP adapters (they already pass `context.mcpReq.signal`), no change to service signatures (they already accept `ExecutionOptions`), and no change to the pure comparison commands. The change is additive at the adapter boundary and does not expand what any command reads, launches, injects, or mutates. Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope). This is a routine bug fix inside existing boundaries, so no advance maintainer scope decision is required; the linked scope decision is "not applicable". ## Contract and boundary impact - Semantic owner and earliest changed stage: The CLI command adapter owns interrupt handling. The earliest changed stage is the adapter boundary, where the handlers previously registered without a cancellation listener. Domain, application, and provider layers are unchanged. - Contribution scope fit and linked maintainer decision: Routine fix within existing boundaries; no new provider, target execution, injection, mutation, or protection recovery. Not applicable. - CLI and MCP/tool-catalog contract: No command name, argument, option, or JSON field changes. The only observable change is that an interrupted command now exits `130`/`143` and returns the existing `cancelled` result instead of exiting `1` with a provider failure. MCP is untouched and already cancels through `context.mcpReq.signal`. No generated tool catalog changes. - Provider, bridge, target-format, or platform compatibility: Applies to all platforms. POSIX delivers a real `SIGINT`/`SIGTERM`; Windows cannot deliver a catchable signal through `child.kill(signal)`, so the test exercises the same handler through an in-process relay. No CDP, Inspector, or bridge protocol change. - Evidence, artifact, provenance, or reconstruction contract: Unchanged. Cancellation still produces the typed `cancelled` result with `cleanup: "complete"`; no artifact is published for a cancelled observation, and partial evidence semantics are unchanged. - Target selection, authority, upstream behavior, and cleanup details (if applicable): These commands only connect to a caller-supplied loopback CDP/Inspector endpoint and read page or runtime evidence. The change adds awaited cleanup on interrupt; it does not add target selection, launch, injection, or mutation authority. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact (CI generates and validates ignored catalogs and `skills/`; edit authored instructions in `.agents/skills/reverse-engineer-anything/` and never commit build outputs): None. No contract, schema, catalog, package, or installation surface changes. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [x] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. The two checked items describe the commands' existing passive behavior: they read page and runtime evidence and connect to a caller-supplied loopback CDP/Inspector endpoint. This PR changes none of those effects; it only adds awaited cleanup when the caller interrupts. ## Evidence and regression coverage - Tests added or updated: `tests/acceptance/passiveObservationCliCancellation.test.ts` drives all eight commands through a real `rea` process against a stalled loopback server, and asserts that the process exits `130`/`143`, that it was not force-terminated, and that stdout carries the `cancelled` result with `cleanup: "complete"` and the matching `operation`. It runs 16 cases (eight commands across `SIGINT` and `SIGTERM`). `tests/fixtures/passiveObservationCancellation.mjs` is the relay hook that re-emits the signal in-process on Windows, where the OS cannot deliver a catchable signal to a child. - Base reproduction or other evidence: On the unmodified base revision the test fails 16 of 16 cases: the interrupt force-terminates the child (observed `terminationSignal: "SIGINT"` with a null exit code) or the stalled discovery eventually fails and the command exits `1` with a provider failure instead of the `cancelled` result. With the fix in place all 16 cases pass. The regression therefore fails on the affected base revision and passes after the change. - User-visible CLI/MCP output (if applicable): An interrupted command now resolves the typed cancellation instead of a provider failure. The asserted fields are: ```json { "code": "cancelled", "category": "cancelled", "details": { "cleanup": "complete", "operation": "list_browser_targets" } } ``` - Remaining proof gaps: The acceptance test uses a stalled loopback fixture rather than a real browser or Electron target, which is deliberate: the assertion is about the adapter's interrupt wiring and awaited cleanup, and a real target would add a racing window without changing what is proven. On this Windows host the OS-signal lane is skipped and the relay lane runs; the OS-signal lane is exercised on the Linux CI shard. No real-browser or real-Electron cancellation run was performed here. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [ ] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. The middle item is left unchecked because a cancelled observation publishes no artifact, so artifact identity and provenance are not exercised by this change. The typed `cancelled` result keeps the interrupted outcome visible rather than collapsing it into a generic failure. ## Validation performed - `npm run test:focused -- tests/acceptance/passiveObservationCliCancellation.test.ts` — 16 passed, 16 skipped (the OS-signal lane is skipped on Windows; the relay lane runs). On the reverted base revision the same file reports 16 failed, 16 skipped. - `npm run check:fast` — passed (cached typecheck and lint, plus the pre-push check). - `git diff --stat upstream/main...HEAD` — 5 files changed, 420 insertions, 165 deletions. ## Compatibility, safety, and release - Breaking changes or migration steps: None. Exit status for an interrupted command changes from `1` to the conventional `130`/`143`, which is the fix, not a break. Command names, arguments, options, and JSON fields are unchanged. - Real Hopper/Ghidra, browser, or OS coverage: No Hopper or Ghidra involvement. The regression drives the real `rea` binary against a stalled loopback CDP/Inspector endpoint. A real browser or Electron target was not exercised; see the remaining proof gap above. - Package or release metadata impact: None. No package, lockfile, version, or generated metadata change. - Security, privacy, process, or containment review: The change only adds a signal listener that aborts in-flight work and removes itself in `finally`. It does not add target selection, execution, injection, mutation, persistence, or new network behavior, and it does not weaken any existing boundary. No secrets or sensitive values are involved. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. On the issue link: no open issue tracks this exact gap. Issue #1126 covers the sibling web-runtime commands and was fixed by the same pattern, so this PR extends that fix to the browser and JavaScript-runtime commands rather than reopening a closed issue. If maintainers prefer, this can be filed as a follow-up issue first. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:06:50 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#5463
No description provided.