Skip to content

chore(release): promote staging to production - #3004

Closed
RemiBonnet wants to merge 5 commits into
mainfrom
staging
Closed

RemiBonnet wants to merge 5 commits into
mainfrom
staging

Conversation

@RemiBonnet

@RemiBonnet RemiBonnet commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Opens the production promotion path from staging to main. Merging this PR triggers semantic-release, then the existing production deployment workflow.

Do not squash this PR. Use a merge commit to preserve the original conventional commits for semantic-release.


Summary by cubic

Promotes the staging branch to production, releasing agent task overlay saves, template resource minimums, CSP-compliant tag loading, dry-run badges, and AWS quota warnings on cluster deployments.

  • Agent Task Connections, Automations, Outputs, and Advanced settings now save directly from their modals or side panels, which are locked against dismissal while saving and stay open with an error if persistence fails; no-op saves are skipped.
  • Enforces 1000 mCPU and 2048 MiB minimums for Agent Task resources, with validation errors in the creation flow, settings page, and every template.
  • Loads GTM and Snitcher as external scripts instead of inline snippets so the CSP can drop 'unsafe-inline'; removes @elgorditosalsero/react-gtm-hook.
  • Cluster deployment lists show a Dry run badge.
  • Cluster overview shows a yellow warning callout when the cluster reports an active AWS quota warning, and the status dot tooltip reads "Quota issue" then.
  • Updates qovery-typescript-axios to 1.1.981 and qovery-ws-typescript-axios to 0.1.666.

Written for commit 7149a68. Summary will update on new commits.

Review in cubic

rmnbrd and others added 4 commits September 22, 2026 15:14
* feat(clusters): add dry runs to the list of cluster deployments

* Update qovery-typescript-axios version

* fix(clusters): use typed dry run status

* test(clusters): cover non-dry run badge state
## Summary

- Save Agent Task Connections, Automations, Outputs, and Advanced
settings directly from their modal or side panel.
- Remove the page-level Save action from settings pages that are
configured through overlays.
- Keep overlays open and display an error when persistence fails.
- Skip the update request when the edited settings did not change.
- Keep MCP selection changes local to the side panel until Save is
selected.

## Screenshots / Recordings

Not included.

## Testing

- [x] Changes tested locally in the relevant Console components
- [x] Targeted Jest suites: 62 tests passed
- [x] `yarn format`
- [x] Targeted ESLint checks
- [x] TypeScript checks for the affected libraries

## PR Checklist

- [x] I followed naming, styling, and TypeScript rules (see
`.cursor/rules`)
- [x] I performed a self-review (diff inspected, dead code removed)
- [x] I titled the PR using [Conventional
Commits](https://www.conventionalcommits.org/en/v1.0.0/) with a scope
when possible
- [x] I only kept necessary comments, written in English
- [x] I involved a designer to validate UI changes if I am not a
designer
- [x] I covered new business logic with tests (unit)
- [x] I confirmed CI is green (Codecov red can be accepted)
- [x] I reviewed and executed locally any AI-assisted code


<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Saves Agent Task Connections, Automations, Outputs, and Advanced
settings directly from their modal or slide-over instead of the
page-level Save button, which is removed on those pages. Overlays now
stay open with an error when persistence fails, and the update request
is skipped when nothing changed. When context services are still
loading, an overlay save keeps the persisted service context and prompt
intact instead of rebuilding them.

- MCP selection changes stay local to the side panel until Save is
selected, rather than applying immediately.
- Overlays are locked against dismissal while a save is in flight.

<sup>Written for commit e2b0222.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/Qovery/console/pull/3001?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
* fix(agent-task): increase template memory

* fix(agent-task): set template resource minimums

* fix(agent-task): enforce resource minimums

* fix(agent-task): surface resource validation errors

* fix(agent-task): clarify required resource errors

* fix(agent-task): keep optional resources optional

* fix(agent-task): align minimum memory to 2 GiB
* fix(gtm): load GTM and Snitcher without inline scripts

Cloudflare is adding a `script-src` directive to the Content-Security-Policy
of console*.qovery.com. The console injected inline JavaScript at runtime,
which only runs under a CSP that allows `'unsafe-inline'`. That keyword
lets any injected inline script run, which removes most of the XSS
protection `script-src` is meant to add. This PR removes the inline scripts
the console is responsible for, so `'unsafe-inline'` can be dropped from
the policy.

Where the inline scripts came from:

- `@elgorditosalsero/react-gtm-hook` (`GTMProvider`) injected two inline
  `<script>` elements through `innerHTML`: the `dataLayer` bootstrap and
  the GTM loader snippet.
- Snitcher was loaded by a GTM Custom HTML tag, which GTM injects as an
  inline script.

Changes:

- Remove the `@elgorditosalsero/react-gtm-hook` dependency.
- Add `loadGoogleTagManager(id)` and `pushToDataLayer(data)` to
  `@qovery/shared/util-js`. They do the same bootstrap as the official GTM
  snippet (`gtm.start` event, async `gtm.js?id=` script), but the script
  tag is created from the bundle as an external script.
- Add `loadSnitcher(profileId)` to `@qovery/shared/util-js`. It injects
  `https://snid.snitcher.com/<id>.js`. The current Snitcher loader starts
  on its own (`window.Snitcher`), so the old inline `snid` queue and the
  `snid('verify')` call are not needed.
- `main.tsx`: load GTM and Snitcher at startup when `NX_PUBLIC_GTM` is set,
  instead of wrapping the app in `GTMProvider`.
- `OnboardingProject`: send `onboarding-organization-created` with
  `pushToDataLayer` instead of `useGTMDispatch`.

Behavior changes:

- GTM is not loaded when `NX_PUBLIC_GTM` is empty. The hook used to request
  `gtm.js?id=undefined`.
- GTM starts before the first React render instead of in an effect.
- The GTM `<noscript>` iframe is gone. It never applies, since the console
  requires JavaScript.

Related changes outside this repo:

- GTM container GTM-PJ39JGR:
  - Delete the Twitter and Reddit Custom HTML tags. They duplicate the
    built-in Twitter tag and the Reddit Pixel template.
  - Delete the Common Room, Reb2b and Clearbit Custom HTML tags. Their
    vendor endpoints return 404 or 403, so the tags already do nothing.
  - Exclude console*.qovery.com from the Snitcher Custom HTML tag, since
    the console now loads Snitcher itself. Until then, Snitcher's loader
    ignores the second initialization.
- Cloudflare rule "CSP for console*.qovery.com" (qovery-infra-as-code) adds
  `script-src`, `worker-src 'self' blob:` and `object-src 'none'`. It keeps
  `'unsafe-inline'` until this PR is deployed. Removing it before the
  deploy would block the old inline GTM loader and stop all tracking.

- [x] `yarn nx test shared-util-js` (134 tests) and
      `yarn nx test domains-onboarding-feature` (39 tests) pass. New specs
      cover both loaders and the onboarding dataLayer event.
- [x] `yarn prettier --check` on the changed files
- [x] `yarn nx lint` for console, shared-util-js and
      domains-onboarding-feature
- [x] Production build: no inline `<script>` in `index.html`, no GTM
      snippet left in the bundle, and both external loaders are present

* refactor(gtm): scope tracking utilities to console

---------

Co-authored-by: RemiBonnet <rbonnet@qovery.com>
@nx-cloud

nx-cloud Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit f2a16ef

Command Status Duration Result
nx run console:build --parallel=3 --configurati... ✅ Succeeded 3s View ↗
nx affected --target=test --parallel=3 --config... ✅ Succeeded <1s View ↗
nx affected --target=lint --parallel=3 ✅ Succeeded 4m 18s View ↗
nx-cloud record -- yarn nx format:check ✅ Succeeded 7s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-09-23 14:48:46 UTC

@codecov

codecov Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.61272% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.89%. Comparing base (fbb27ee) to head (7149a68).
⚠️ Report is 48 commits behind head on main.

Files with missing lines Patch % Lines
...ed-settings/agentic-workflow-advanced-settings.tsx 31.81% 13 Missing and 2 partials ⚠️
...tus-indicator/cluster-running-status-indicator.tsx 53.84% 0 Missing and 6 partials ⚠️
...settings/agentic-workflow-connections-settings.tsx 71.42% 6 Missing ⚠️
libs/shared/ui/src/lib/components/modal/modal.tsx 42.85% 3 Missing and 1 partial ⚠️
.../util-services/src/lib/get-service-state-colors.ts 0.00% 2 Missing ⚠️
...er-quota-warning/cluster-quota-warning-callout.tsx 87.50% 1 Missing ⚠️
...settings/agentic-workflow-automations-settings.tsx 66.66% 1 Missing ⚠️
...tic-workflow-configuration/sheet/overlay-sheet.tsx 75.00% 1 Missing ⚠️
...i/src/lib/components/modal/use-modal/use-modal.tsx 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3004      +/-   ##
==========================================
- Coverage   51.58%   50.89%   -0.70%     
==========================================
  Files        1261     1378     +117     
  Lines       27572    29919    +2347     
  Branches     8322     8858     +536     
==========================================
+ Hits        14222    15226    +1004     
- Misses      10997    12198    +1201     
- Partials     2353     2495     +142     
Flag Coverage Δ
unittests 50.89% <78.61%> (-0.70%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

14 issues found across 41 files

Confidence score: 2/5

  • agentic-workflow-advanced-settings.tsx allows Add/Edit to start another save while an async delete save is pending, so overlapping full-form payloads can overwrite one another—disable the action until the mutation settles.
  • modal.tsx and use-modal.tsx do not pass the injected setModalDismissible prop to content opened through useModal, leaving dismissibility behavior inconsistent—forward the prop through that rendering path.
  • agentic-workflow-resources.ts accepts non-finite numeric conversions such as 1e309, allowing invalid resource values to submit—reject them with Number.isFinite before minimum validation.
  • apps/console/src/main.tsx now loads Snitcher whenever GTM is configured, bypassing the prior GTM consent, trigger, and environment controls—retain GTM governance or reproduce those conditions before loading it.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-resources.ts">

<violation number="1" location="libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-resources.ts:5">
P2: This lower-bound check treats `Infinity` values such as `1e309` as valid, allowing the form to submit an invalid resource value. Reject non-finite conversions with `Number.isFinite` before applying the minimum checks.</violation>
</file>

<file name="libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-automations-settings/agentic-workflow-automations-settings.tsx">

<violation number="1" location="libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-automations-settings/agentic-workflow-automations-settings.tsx:22">
P2: The fallback clears the form's dirty state instead of marking the automation change dirty. Use `setValue('automation', ..., { shouldDirty: true })` in the fallback so callers without `onSave` can still detect and submit the change.</violation>
</file>

<file name="libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-advanced-settings/agentic-workflow-advanced-settings.tsx">

<violation number="1" location="libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-advanced-settings/agentic-workflow-advanced-settings.tsx:44">
P3: This asynchronous save error has no accessible alert role, so assistive technologies may not announce it. Add `role="alert"` to the error paragraph.</violation>

<violation number="2" location="libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-advanced-settings/agentic-workflow-advanced-settings.tsx:113">
P1: The new async delete save does not prevent the Add/Edit button from opening another save while `isSaving` is true. Disable that action until the mutation settles; otherwise overlapping full-form payloads can overwrite each other.</violation>

<violation number="3" location="libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-advanced-settings/agentic-workflow-advanced-settings.tsx:142">
P3: A failed delete leaves `deleteError` visible after a later successful edit because the code-save path never clears it. Clear the delete error when starting the edit save.</violation>
</file>

<file name="apps/console/src/main.tsx">

<violation number="1" location="apps/console/src/main.tsx:101">
P2: `loadSnitcher` now runs on every page whenever `GTM` is configured, bypassing the former GTM Custom HTML tag’s consent, trigger, and environment conditions. Keep Snitcher controlled by GTM or replicate those conditions before loading it; otherwise it can also run twice while the existing container tag remains.</violation>
</file>

<file name="libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/mcp/mcp-sheet.spec.tsx">

<violation number="1" location="libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/mcp/mcp-sheet.spec.tsx:209">
P3: This test announces the error but never verifies the failure keeps the sheet open. A regression that lets onSave reject yet still close the sheet (or call onClose) would pass this test. Hoist onClose to a variable and assert it was not called after the failed save.</violation>
</file>

<file name="libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/sheet/overlay-sheet.spec.tsx">

<violation number="1" location="libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/sheet/overlay-sheet.spec.tsx:48">
P3: The new test only covers the negative path (Escape does nothing when `dismissible={false}`). No test anywhere presses Escape and expects `onClose` for the default `dismissible` path, so a regression that silently drops Escape-to-close for enabled sheets would go uncaught. Add a companion test with the default `dismissible` that asserts `onClose` is called on Escape.</violation>
</file>

<file name="libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.spec.tsx">

<violation number="1" location="libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.spec.tsx:728">
P3: The `data-state: 'open'` assertion is vacuous: this test opens the Resources accordion at the start, so it is already open when `Create` is clicked and the assertion passes no matter what. Assert the error class inside `waitFor` (matching the environment-variables test, where the section state change is async), and only keep that which Create actually triggers.</violation>
</file>

<file name="libs/shared/ui/src/lib/components/modal/modal.tsx">

<violation number="1" location="libs/shared/ui/src/lib/components/modal/modal.tsx:178">
P1: The `setModalDismissible` prop injected via `cloneElement` never reaches modal content rendered through `useModal`. `useModal` stores content as `setContentModal(<>{modal.content}</>)` (use-modal.tsx), and `ModalProvider` renders it inside `<Modal>`, so Modal's `children` is a Fragment element; `cloneElement` attaches `setModalDismissible` to the Fragment itself, where React silently drops it. Consumers such as `GitContextModal`/`QoveryServiceContextModal` rely on this prop to lock dismissal during async save (`setModalDismissible?.(false)`), and one of their call sites, `agentic-workflow-configuration.tsx` `openGitContext`/`openQoveryServiceContext`, does not pass the prop explicitly — the new dismissal guard is a silent no-op there, leaving the modal dismissible mid-save. Forward the control props onto the actual content element (e.g. wrap the portal content in a passing-through wrapper or clone the element directly in `useModal`), and have explicit consumer props take precedence.</violation>
</file>

<file name="libs/domains/onboarding/feature/src/lib/onboarding-project/onboarding-project.spec.tsx">

<violation number="1" location="libs/domains/onboarding/feature/src/lib/onboarding-project/onboarding-project.spec.tsx:110">
P3: In the success-case test, `window.dataLayer` may still be undefined if the flow stops before `pushToDataLayer` runs, so `toContainEqual` reports a confusing "must be iterable" error instead of showing the missing event. Use `window.dataLayer ?? []` for consistency with the failure-case assertion below.</violation>
</file>

<file name="libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-settings.tsx">

<violation number="1" location="libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-settings.tsx:271">
P2: `persistSettings` snapshots `form.getValues()` before `await editService(...)` and then calls `form.reset({ ...data, agentPrompt, contextServiceIds })`. While the mutation is pending, the form inputs stay editable (only the Save buttons show a loading state), so any value the user types during the request is silently overwritten by the pre-request snapshot the moment the save resolves. Fix by resetting with `keepDirtyValues: true` so fields edited after the save started keep their current values.</violation>
</file>

<file name="apps/console/src/utils/snitcher.ts">

<violation number="1" location="apps/console/src/utils/snitcher.ts:4">
P2: `loadSnitcher` assumes the profile script at `snid.snitcher.com/<id>.js` bootstraps on its own, but the documented embed for this exact URL pattern also creates the `window.snid` queue and calls `snid("verify", "<trackingId>")`. If that verify call (or its equivalent) is required, Snitcher will load the script but never start tracking, silently losing the onboarding analytics the GTM Custom HTML tag used to send. Verify the self-bootstrap assumption in staging (e.g., confirm the script fires beacons/requests with this loader and a fresh profile) before promoting, since the unit tests only assert the constructed `src` string and cannot catch a non-functional embed.</violation>
</file>

<file name="libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/mcp/mcp-sheet.tsx">

<violation number="1" location="libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/mcp/mcp-sheet.tsx:250">
P3: `McpSheet` never tracks its own pending save: `loading`, `disabled`, and `dismissible={!isSaving}` are all driven by the parent-supplied `isSaving` prop. In the settings page that prop is `useEditService().isLoading`, so between clicking Save and the parent re-rendering with the mutation loading flag, the sheet is still dismissible — Escape/backdrop can unmount it mid-request, and a then-failing save silently loses its error alert (the sheet holding `saveError` is gone). `AutomationSheet`, `GitContextModal`, and `QoveryServiceContextModal` all manage a local `setIsSaving`, so this sheet is the odd one out. Track `isSaving` locally in the Save handler (as AutomationSheet does) instead of relying on the parent's mutation state.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

size="xs"
iconOnly
loading={isSaving}
disabled={isSaving}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: The new async delete save does not prevent the Add/Edit button from opening another save while isSaving is true. Disable that action until the mutation settles; otherwise overlapping full-form payloads can overwrite each other.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-advanced-settings/agentic-workflow-advanced-settings.tsx, line 113:

<comment>The new async delete save does not prevent the Add/Edit button from opening another save while `isSaving` is true. Disable that action until the mutation settles; otherwise overlapping full-form payloads can overwrite each other.</comment>

<file context>
@@ -79,13 +109,27 @@ export function AgenticWorkflowAdvancedSettings({ form }: { form: UseFormReturn<
               size="xs"
               iconOnly
+              loading={isSaving}
+              disabled={isSaving}
               aria-label="Delete Dockerfile fragment"
-              onClick={() => form.setValue('dockerFragment', '', { shouldDirty: true })}
</file context>

<div className={`${fullScreen || height ? 'h-full overflow-hidden' : 'max-h-[80vh] overflow-auto'}`}>
{cloneElement(children, {
setOpen: setExternalOpen ? setExternalOpen : setOpen,
setModalDismissible: setContentDismissible,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: The setModalDismissible prop injected via cloneElement never reaches modal content rendered through useModal. useModal stores content as setContentModal(<>{modal.content}</>) (use-modal.tsx), and ModalProvider renders it inside <Modal>, so Modal's children is a Fragment element; cloneElement attaches setModalDismissible to the Fragment itself, where React silently drops it. Consumers such as GitContextModal/QoveryServiceContextModal rely on this prop to lock dismissal during async save (setModalDismissible?.(false)), and one of their call sites, agentic-workflow-configuration.tsx openGitContext/openQoveryServiceContext, does not pass the prop explicitly — the new dismissal guard is a silent no-op there, leaving the modal dismissible mid-save. Forward the control props onto the actual content element (e.g. wrap the portal content in a passing-through wrapper or clone the element directly in useModal), and have explicit consumer props take precedence.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At libs/shared/ui/src/lib/components/modal/modal.tsx, line 178:

<comment>The `setModalDismissible` prop injected via `cloneElement` never reaches modal content rendered through `useModal`. `useModal` stores content as `setContentModal(<>{modal.content}</>)` (use-modal.tsx), and `ModalProvider` renders it inside `<Modal>`, so Modal's `children` is a Fragment element; `cloneElement` attaches `setModalDismissible` to the Fragment itself, where React silently drops it. Consumers such as `GitContextModal`/`QoveryServiceContextModal` rely on this prop to lock dismissal during async save (`setModalDismissible?.(false)`), and one of their call sites, `agentic-workflow-configuration.tsx` `openGitContext`/`openQoveryServiceContext`, does not pass the prop explicitly — the new dismissal guard is a silent no-op there, leaving the modal dismissible mid-save. Forward the control props onto the actual content element (e.g. wrap the portal content in a passing-through wrapper or clone the element directly in `useModal`), and have explicit consumer props take precedence.</comment>

<file context>
@@ -171,8 +175,9 @@ export const Modal = (props: ModalProps) => {
           <div className={`${fullScreen || height ? 'h-full overflow-hidden' : 'max-h-[80vh] overflow-auto'}`}>
             {cloneElement(children, {
               setOpen: setExternalOpen ? setExternalOpen : setOpen,
+              setModalDismissible: setContentDismissible,
             })}
-            {buttonClose && dismissible && (
</file context>

export const AGENTIC_WORKFLOW_MIN_RAM_MIB = 2048

export function areAgenticWorkflowResourcesValid(cpu: string, memory: string) {
return Number(cpu) >= AGENTIC_WORKFLOW_MIN_CPU_MILLI && Number(memory) >= AGENTIC_WORKFLOW_MIN_RAM_MIB

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: This lower-bound check treats Infinity values such as 1e309 as valid, allowing the form to submit an invalid resource value. Reject non-finite conversions with Number.isFinite before applying the minimum checks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-resources.ts, line 5:

<comment>This lower-bound check treats `Infinity` values such as `1e309` as valid, allowing the form to submit an invalid resource value. Reject non-finite conversions with `Number.isFinite` before applying the minimum checks.</comment>

<file context>
@@ -0,0 +1,6 @@
+export const AGENTIC_WORKFLOW_MIN_RAM_MIB = 2048
+
+export function areAgenticWorkflowResourcesValid(cpu: string, memory: string) {
+  return Number(cpu) >= AGENTIC_WORKFLOW_MIN_CPU_MILLI && Number(memory) >= AGENTIC_WORKFLOW_MIN_RAM_MIB
+}
</file context>
Suggested change
return Number(cpu) >= AGENTIC_WORKFLOW_MIN_CPU_MILLI && Number(memory) >= AGENTIC_WORKFLOW_MIN_RAM_MIB
const cpuValue = Number(cpu)
const memoryValue = Number(memory)
return (
Number.isFinite(cpuValue) &&
cpuValue >= AGENTIC_WORKFLOW_MIN_CPU_MILLI &&
Number.isFinite(memoryValue) &&
memoryValue >= AGENTIC_WORKFLOW_MIN_RAM_MIB
)

const saveSettings: SaveAgenticWorkflowSettings =
onSave ??
(async (values) => {
form.reset({ ...form.getValues(), ...values })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The fallback clears the form's dirty state instead of marking the automation change dirty. Use setValue('automation', ..., { shouldDirty: true }) in the fallback so callers without onSave can still detect and submit the change.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-automations-settings/agentic-workflow-automations-settings.tsx, line 22:

<comment>The fallback clears the form's dirty state instead of marking the automation change dirty. Use `setValue('automation', ..., { shouldDirty: true })` in the fallback so callers without `onSave` can still detect and submit the change.</comment>

<file context>
@@ -2,18 +2,25 @@ import { useState } from 'react'
+  const saveSettings: SaveAgenticWorkflowSettings =
+    onSave ??
+    (async (values) => {
+      form.reset({ ...form.getValues(), ...values })
+    })
   const schedule = automation.triggers.find((trigger) => trigger.type === 'schedule')
</file context>
Suggested change
form.reset({ ...form.getValues(), ...values })
if (values.automation) form.setValue('automation', values.automation, { shouldDirty: true })

Comment thread apps/console/src/main.tsx
if (GTM) {
loadGoogleTagManager(GTM)
// Snitcher was previously injected by a GTM Custom HTML tag, so it keeps following the GTM setup
loadSnitcher(SNITCHER_PROFILE_ID)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: loadSnitcher now runs on every page whenever GTM is configured, bypassing the former GTM Custom HTML tag’s consent, trigger, and environment conditions. Keep Snitcher controlled by GTM or replicate those conditions before loading it; otherwise it can also run twice while the existing container tag remains.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/console/src/main.tsx, line 101:

<comment>`loadSnitcher` now runs on every page whenever `GTM` is configured, bypassing the former GTM Custom HTML tag’s consent, trigger, and environment conditions. Keep Snitcher controlled by GTM or replicate those conditions before loading it; otherwise it can also run twice while the existing container tag remains.</comment>

<file context>
@@ -92,6 +94,13 @@ posthog.init(POSTHOG, {
+if (GTM) {
+  loadGoogleTagManager(GTM)
+  // Snitcher was previously injected by a GTM Custom HTML tag, so it keeps following the GTM setup
+  loadSnitcher(SNITCHER_PROFILE_ID)
+}
+
</file context>


await userEvent.click(screen.getByRole('button', { name: 'Save' }))

expect(await screen.findByRole('alert')).toHaveTextContent('Unable to save the MCP selection. Try again.')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This test announces the error but never verifies the failure keeps the sheet open. A regression that lets onSave reject yet still close the sheet (or call onClose) would pass this test. Hoist onClose to a variable and assert it was not called after the failed save.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/mcp/mcp-sheet.spec.tsx, line 209:

<comment>This test announces the error but never verifies the failure keeps the sheet open. A regression that lets onSave reject yet still close the sheet (or call onClose) would pass this test. Hoist onClose to a variable and assert it was not called after the failed save.</comment>

<file context>
@@ -168,4 +168,44 @@ describe('McpSheet', () => {
+
+    await userEvent.click(screen.getByRole('button', { name: 'Save' }))
+
+    expect(await screen.findByRole('alert')).toHaveTextContent('Unable to save the MCP selection. Try again.')
+  })
 })
</file context>

it('ignores Escape while dismissal is disabled', async () => {
const onClose = jest.fn()
const { userEvent } = renderWithProviders(
<OverlaySheet dismissible={false} onClose={onClose}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The new test only covers the negative path (Escape does nothing when dismissible={false}). No test anywhere presses Escape and expects onClose for the default dismissible path, so a regression that silently drops Escape-to-close for enabled sheets would go uncaught. Add a companion test with the default dismissible that asserts onClose is called on Escape.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/sheet/overlay-sheet.spec.tsx, line 48:

<comment>The new test only covers the negative path (Escape does nothing when `dismissible={false}`). No test anywhere presses Escape and expects `onClose` for the default `dismissible` path, so a regression that silently drops Escape-to-close for enabled sheets would go uncaught. Add a companion test with the default `dismissible` that asserts `onClose` is called on Escape.</comment>

<file context>
@@ -41,4 +41,17 @@ describe('OverlaySheet', () => {
+  it('ignores Escape while dismissal is disabled', async () => {
+    const onClose = jest.fn()
+    const { userEvent } = renderWithProviders(
+      <OverlaySheet dismissible={false} onClose={onClose}>
+        <p>Sheet body</p>
+      </OverlaySheet>
</file context>

await userEvent.click(screen.getByRole('button', { name: 'Create' }))

const resourcesTrigger = screen.getByRole('button', { name: /Resources/ })
expect(resourcesTrigger).toHaveAttribute('data-state', 'open')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The data-state: 'open' assertion is vacuous: this test opens the Resources accordion at the start, so it is already open when Create is clicked and the assertion passes no matter what. Assert the error class inside waitFor (matching the environment-variables test, where the section state change is async), and only keep that which Create actually triggers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.spec.tsx, line 728:

<comment>The `data-state: 'open'` assertion is vacuous: this test opens the Resources accordion at the start, so it is already open when `Create` is clicked and the assertion passes no matter what. Assert the error class inside `waitFor` (matching the environment-variables test, where the section state change is async), and only keep that which Create actually triggers.</comment>

<file context>
@@ -702,6 +702,34 @@ describe('AgenticWorkflowConfiguration', () => {
+    await userEvent.click(screen.getByRole('button', { name: 'Create' }))
+
+    const resourcesTrigger = screen.getByRole('button', { name: /Resources/ })
+    expect(resourcesTrigger).toHaveAttribute('data-state', 'open')
+    expect(resourcesTrigger).toHaveClass('bg-surface-negative-subtle')
+    expect(mockCreateService).not.toHaveBeenCalled()
</file context>

await userEvent.type(screen.getByLabelText('Organization name'), 'Acme')
await userEvent.click(screen.getByRole('button', { name: 'Continue' }))

expect(window.dataLayer).toContainEqual({ event: 'onboarding-organization-created', plan: 'BUSINESS_2025' })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: In the success-case test, window.dataLayer may still be undefined if the flow stops before pushToDataLayer runs, so toContainEqual reports a confusing "must be iterable" error instead of showing the missing event. Use window.dataLayer ?? [] for consistency with the failure-case assertion below.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At libs/domains/onboarding/feature/src/lib/onboarding-project/onboarding-project.spec.tsx, line 110:

<comment>In the success-case test, `window.dataLayer` may still be undefined if the flow stops before `pushToDataLayer` runs, so `toContainEqual` reports a confusing "must be iterable" error instead of showing the missing event. Use `window.dataLayer ?? []` for consistency with the failure-case assertion below.</comment>

<file context>
@@ -103,4 +100,25 @@ describe('OnboardingProject', () => {
+    await userEvent.type(screen.getByLabelText('Organization name'), 'Acme')
+    await userEvent.click(screen.getByRole('button', { name: 'Continue' }))
+
+    expect(window.dataLayer).toContainEqual({ event: 'onboarding-organization-created', plan: 'BUSINESS_2025' })
+  })
+
</file context>
Suggested change
expect(window.dataLayer).toContainEqual({ event: 'onboarding-organization-created', plan: 'BUSINESS_2025' })
expect(window.dataLayer ?? []).toContainEqual({ event: 'onboarding-organization-created', plan: 'BUSINESS_2025' })

type="button"
className="w-full justify-center"
size="lg"
loading={isSaving}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: McpSheet never tracks its own pending save: loading, disabled, and dismissible={!isSaving} are all driven by the parent-supplied isSaving prop. In the settings page that prop is useEditService().isLoading, so between clicking Save and the parent re-rendering with the mutation loading flag, the sheet is still dismissible — Escape/backdrop can unmount it mid-request, and a then-failing save silently loses its error alert (the sheet holding saveError is gone). AutomationSheet, GitContextModal, and QoveryServiceContextModal all manage a local setIsSaving, so this sheet is the odd one out. Track isSaving locally in the Save handler (as AutomationSheet does) instead of relying on the parent's mutation state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/mcp/mcp-sheet.tsx, line 250:

<comment>`McpSheet` never tracks its own pending save: `loading`, `disabled`, and `dismissible={!isSaving}` are all driven by the parent-supplied `isSaving` prop. In the settings page that prop is `useEditService().isLoading`, so between clicking Save and the parent re-rendering with the mutation loading flag, the sheet is still dismissible — Escape/backdrop can unmount it mid-request, and a then-failing save silently loses its error alert (the sheet holding `saveError` is gone). `AutomationSheet`, `GitContextModal`, and `QoveryServiceContextModal` all manage a local `setIsSaving`, so this sheet is the odd one out. Track `isSaving` locally in the Save handler (as AutomationSheet does) instead of relying on the parent's mutation state.</comment>

<file context>
@@ -216,9 +237,29 @@ export function McpSheet({
+          type="button"
+          className="w-full justify-center"
+          size="lg"
+          loading={isSaving}
+          disabled={isSaving}
+          onClick={async () => {
</file context>

* feat(clusters): surface AWS quota warning on cluster status

While the cluster status websocket reports an active AWS quota warning
(Karpenter cannot create new nodes because a quota is reached, QOV-2276),
show it in the Console:

- Cluster overview: a yellow warning Callout with the quota name, impacted
  resource, message and the suggested action to run in AWS.
- Service cluster-status dot: the tooltip reads "Quota issue" instead of
  "Warning" while the quota warning is active.

The quota_warning field on computed_status is not yet exposed by
qovery-ws-typescript-axios, so it is read through a local type
(getClusterQuotaWarning) until the generated package includes it.

* docs(agents): document route parameter access

* fix(clusters): use generated quota warning type

* test(clusters): stabilize quota warning status test

---------

Co-authored-by: qovery-agent <qovery-agent@qovery.com>
@RemiBonnet RemiBonnet closed this Sep 23, 2026
auto-merge was automatically disabled September 23, 2026 15:41

Pull request was closed

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.

3 participants