Skip to content

[dev] [tofikwest] feat/browser-automation-ui#3417

Open
github-actions[bot] wants to merge 66 commits into
mainfrom
feat/browser-automation-ui
Open

[dev] [tofikwest] feat/browser-automation-ui#3417
github-actions[bot] wants to merge 66 commits into
mainfrom
feat/browser-automation-ui

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

This is an automated pull request to merge feat/browser-automation-ui into dev.
It was created by the [Auto Pull Request] action.


Summary by cubic

Builds a smart connect-and-capture flow for browser evidence with login analysis, 1Password-backed auto sign-in, a live instruction test, and one-click reconnect for expired sessions. SSO auto-navigates to the identity provider and streams steps; valid sessions skip interaction, and reconnect refreshes sessions using stored credentials or SSO as needed.

  • New Features

    • Connect + sign-in: detect methods (password/SSO; passkey is excluded), choose Email & password or SSO, store credentials in a per-org 1Password vault, run the first automated sign-in on a live session with outcome classification (logged in, invalid credentials, needs 2FA, challenge, SSO handoff), auto re-login for needs_reauth, mark profiles verified after success, and update the connection URL to the authenticated home; live view at 1920×1080 with narrated steps, take-over for 2FA/challenges/SSO, a brief “Signed in” confirmation; if only passkey is detected, show a clear “can’t connect here” message.
    • Reconnect: one-click reconnect on expired connections; password profiles auto sign back in with stored credentials, SSO profiles drive to the IdP and hand off; the list refreshes on completion.
    • SSO path: drive to the vendor’s identity provider automatically and stream the steps; if the saved session is still valid, sign in without interaction; otherwise hand off the same live browser to the user to finish.
    • APIs: POST /v1/browserbase/analyze-login, POST /v1/browserbase/profiles/:id/credentials, POST /v1/browserbase/profiles/:id/signin, POST /v1/browserbase/instructions/test, PATCH/DELETE /v1/browserbase/profiles/:id; extra site fields (workspace, subdomain, etc.) are captured and filled by label via variable substitution without exposing values to the model; instruction tests stream step timelines.
    • Execution: navigation uses openai/gpt-5.6-terra with a runtime Claude fallback; page reading/verdicts use a separate model; runs open the specific item named in the instruction, verify the requested page, and evaluate pass/fail against that target; tests have a larger budget with clearer timeouts; evidence screenshots are full-page.
    • App: smart connect wizard with method chooser (password or SSO) and capture-after-verify; after a successful sign-in, auto-advance into writing the first instruction; support multiple vendor connections with per-connection “Add instruction” and “Connect another vendor”; manage connections (edit name/URL, guarded remove); split instruction composer to write and watch a live test with a first-class pass/fail check; composer always starts from the connection; evidence view shows a connection card, inline cadence picker, run-history strip, run-detail overlay, and a 16:9 live preview; analysis runs off-request and can be resumed within 15 minutes.
  • Migration

    • Set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID (required). Optional: BROWSERBASE_CUA_MODEL and BROWSERBASE_STAGEHAND_MODEL.
    • Set OP_SERVICE_ACCOUNT_TOKEN to enable 1Password-backed sign-in; unset falls back to manual re-auth.
    • Subscribe to returned run handles to receive analyze/sign-in and instruction-test results.
    • Build/deploy with @1password/sdk (lazy-loaded and marked external for @trigger.dev builds).

Written for commit 527d7a0. Summary will update on new commits.

Review in cubic

Browser automations can now sign back in on their own when a vendor
session expires, instead of always requiring a person to re-authenticate.

- Store a profile's login (username, password, optional authenticator
  setup key) in a per-organization 1Password vault via a new
  credential-storage service and a POST profiles/:id/credentials endpoint.
- Resolve those credentials just-in-time during a run through the
  previously-stubbed credential vault adapter, now backed by 1Password,
  and drive sign-in with Stagehand variable substitution so secret values
  are never sent to the model.
- Retry once with a freshly generated one-time code to cover the rotation
  boundary; fall back to human re-authentication when a login needs a step
  the agent cannot complete (SMS/email/push/SSO).
- Let the scheduler attempt a run for a needs-reauth profile that has
  stored credentials, and mark a profile verified again after a
  successful run.
- Load the 1Password SDK lazily and mark it external for Trigger.dev
  builds; the feature stays inert unless OP_SERVICE_ACCOUNT_TOKEN is set.

Adds unit tests for the credential resolver and the re-login flow.
Adds the first step of the browser-automation connect wizard: a credentials
form (username, password, optional authenticator setup key) shown before the
live sign-in. On submit it resolves the auth profile and stores the login via
the credentials endpoint so scheduled and manual runs can sign in on their own.
Credential storage failures are non-fatal and fall back to manual sign-in.

Part of the Browser Automations redesign (connect-a-login flow).
Adds unit tests for the form (fields, validation, submit, password toggle).
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Jul 17, 2026 8:36pm
comp-framework-editor Ready Ready Preview, Comment Jul 17, 2026 8:36pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal Skipped Skipped Jul 17, 2026 8:36pm

Request Review

@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.

22 issues found across 27 files

Confidence score: 1/5

  • In apps/api/src/browserbase/browser-auth-profiles.controller.ts, totpSeed is not redacted in audit logging, so raw MFA seeds can be persisted and exposed as sensitive secrets if logs are accessed—add totpSeed to redaction and ship a regression test before merging.
  • In apps/api/src/browserbase/onepassword-credential-vault.adapter.ts, integration:create callers can reference arbitrary op:// items readable by the shared service account, which risks cross-organization credential use—enforce org-scoped authorization on vault item references before merge.
  • In apps/api/src/browserbase/browser-credential-storage.service.ts and apps/api/src/browserbase/browser-auth-profiles.controller.ts, credential saves can mis-store OTP fields and create orphaned replacement 1Password items, leaving stale passwords/TOTP secrets active in the vault—fix OTP field ID handling and update/save logic to reuse or clean up provider-owned items before merging.
  • In apps/api/src/browserbase/browser-automation-execution.service.ts and apps/api/src/browserbase/onepassword-credential-vault.adapter.ts, result ordering and vault error handling can misclassify failures (needs_reauth) and overwrite newer blocked/reauth states, causing incorrect operator actions—guard status updates by version/order and surface vault outages distinctly before merge.
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="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useBrowserContext.ts">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useBrowserContext.ts:65">
P3: Incorrect credential payloads or regressions in the fallback behavior can ship unnoticed because the new credential-storage branch is untested. A hook test covering the credentials POST and an error response that still proceeds to the manual session flow would protect this sensitive path.</violation>

<violation number="2" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useBrowserContext.ts:65">
P2: The auth flow now sends raw login secrets to a new save endpoint before the session is started. If session creation or navigation fails afterward, the profile can end up with stored credentials but no completed connection, so the UI should make that multi-step failure mode explicit or roll the credential save into the same transactional path.</violation>
</file>

<file name="apps/api/src/browserbase/browserbase.service.ts">

<violation number="1" location="apps/api/src/browserbase/browserbase.service.ts:72">
P3: The new credential-storage façade has no focused test verifying that the organization/profile identifiers and secret fields reach `BrowserCredentialStorageService`. A delegation test would catch regressions in this API path without exercising the external vault.</violation>
</file>

<file name="apps/api/src/browserbase/browser-auth-profile.service.ts">

<violation number="1" location="apps/api/src/browserbase/browser-auth-profile.service.ts:209">
P3: The new verification transition is untested, so regressions in clearing `blockedReason`, recording `lastVerifiedAt`, or handling an already-verified profile can pass unnoticed. Adding focused service tests for the missing-profile, already-verified, and state-transition cases would cover this path.</violation>

<violation number="2" location="apps/api/src/browserbase/browser-auth-profile.service.ts:214">
P2: Successful automation runs on an already-verified profile do not refresh `lastVerifiedAt`, so the API can report the original verification time instead of the latest successful authentication. Persist the successful-run timestamp even when the status is already `verified`; only avoid a status-only write if no metadata needs updating.</violation>
</file>

<file name="apps/api/src/browserbase/browser-evidence-execution.ts">

<violation number="1" location="apps/api/src/browserbase/browser-evidence-execution.ts:88">
P3: The new authentication branch can regress without detecting whether `executeBrowserEvidence` resumes actions after successful recovery or returns the expected `needs_reauth` result after each failure mode. A focused execution-level test covering those branches would protect the integration between `checkAuth`, the vault-backed relogin helper, and result classification.</violation>

<violation number="2" location="apps/api/src/browserbase/browser-evidence-execution.ts:97">
P3: `relogin.page` is never used after this assignment: failure returns immediately, and success overwrites `page` during evidence-page resolution. Removing this dead assignment keeps the re-auth flow clearer and avoids implying that the returned page controls the subsequent action.</violation>
</file>

<file name="apps/api/src/browserbase/browser-automation-execution.service.ts">

<violation number="1" location="apps/api/src/browserbase/browser-automation-execution.service.ts:144">
P2: A `needs_reauth` profile with only `vaultProvider` set is treated as auto-relogin-capable even though the credential adapter cannot resolve credentials without `vaultExternalItemRef`; this causes credential-less profiles to consume a full automation attempt before returning `needs_reauth`. Gate this path on the actual stored credential reference (and supported provider) rather than the provider label alone.</violation>

<violation number="2" location="apps/api/src/browserbase/browser-automation-execution.service.ts:144">
P3: The new auto-relogin and success-to-verified transitions are untested, so regressions in credential gating or profile-state recovery can silently reach scheduled runs. Adding execution-service tests for usable versus incomplete vault metadata, successful recovery, and a failed result preserving `needs_reauth` would make this behavior verifiable.</violation>

<violation number="3" location="apps/api/src/browserbase/browser-automation-execution.service.ts:199">
P2: A stale successful run can reset a newer `needs_reauth` or `blocked` result because `markVerified` is applied outside the runner's profile lock and has no result-order/version guard. Serializing profile-result updates or making the transition conditional on the run's completion order would prevent an older run from re-enabling a profile after a later auth failure.</violation>
</file>

<file name="apps/api/src/browserbase/onepassword-credential-vault.adapter.ts">

<violation number="1" location="apps/api/src/browserbase/onepassword-credential-vault.adapter.ts:34">
P1: A caller with `integration:create` can point a profile at any `op://` item the shared service account can read, allowing an automation to use credentials from another organization's vault when the reference is known. The reference should be derived from server-owned storage or validated against the profile's organization/connection before resolving it.</violation>

<violation number="2" location="apps/api/src/browserbase/onepassword-credential-vault.adapter.ts:71">
P2: A 1Password outage or access-denied response is silently converted into missing credentials, so runs report `needs_reauth` and can trigger unnecessary manual reauthentication instead of exposing a vault failure. Only a classified missing-field error should be suppressed; authentication, permission, and transport errors should propagate.</violation>

<violation number="3" location="apps/api/src/browserbase/onepassword-credential-vault.adapter.ts:74">
P2: A malformed or caller-supplied reference can inject lines into application logs, and normal failures disclose vault/item metadata. Logging a fixed error message or a sanitized/structured identifier would avoid treating the raw reference as log text.</violation>
</file>

<file name="apps/api/src/browserbase/browser-credential-storage.service.ts">

<violation number="1" location="apps/api/src/browserbase/browser-credential-storage.service.ts:38">
P3: The new credential-storage workflow has no unit coverage for tenant scoping, vault reuse/creation, TOTP field construction, external failures, or profile-reference persistence. Adding a colocated service spec would catch regressions in this secrets-handling path.</violation>

<violation number="2" location="apps/api/src/browserbase/browser-credential-storage.service.ts:98">
P2: Concurrent credential saves can create multiple vaults for one organization because the list-then-create check is not atomic. A per-organization lock or post-create recheck/deduplication would keep all profiles in the intended single vault.</violation>

<violation number="3" location="apps/api/src/browserbase/browser-credential-storage.service.ts:139">
P1: Providing a TOTP seed does not create the documented 1Password Login OTP field because its ID is set to the display title. Keeping the title for secret references but using the built-in ID `onetimepassword` allows OTP generation and resolution to work reliably.</violation>
</file>

<file name="apps/api/src/browserbase/browser-auth-profiles.controller.ts">

<violation number="1" location="apps/api/src/browserbase/browser-auth-profiles.controller.ts:117">
P2: Audit records for this credential update will be mislabeled as `create` because the route uses POST while requiring `integration:update`. Have the audit action honor the declared permission action (or use an update verb) so compliance history reflects the RBAC decision.</violation>

<violation number="2" location="apps/api/src/browserbase/browser-auth-profiles.controller.ts:125">
P2: Successful credential saves will return HTTP 201, but the OpenAPI contract advertises 200, which can make generated clients treat a valid response as unexpected. Either add `@HttpCode(200)` or document 201 here.</violation>

<violation number="3" location="apps/api/src/browserbase/browser-auth-profiles.controller.ts:129">
P0: The new credentials request can persist the raw TOTP seed in the audit database. `password` is redacted by `SENSITIVE_KEYS`, but `totpSeed` is not, so this endpoint should add TOTP seed redaction (with a regression test) before accepting secrets through an audited mutation.</violation>

<violation number="4" location="apps/api/src/browserbase/browser-auth-profiles.controller.ts:131">
P1: Repeated saves through this endpoint create a new 1Password Login item while leaving the previous item—and its old password/TOTP secret—in the organization vault. Reuse the existing provider-owned item or remove the old item after a successful replacement so credential updates do not accumulate stale secrets.</violation>
</file>

<file name="apps/api/src/browserbase/browser-credential-vault.factory.ts">

<violation number="1" location="apps/api/src/browserbase/browser-credential-vault.factory.ts:14">
P3: The environment-dependent adapter selection is untested, so a change to `OP_SERVICE_ACCOUNT_TOKEN` handling or either return branch could silently break credential resolution. A focused factory test covering configured, whitespace-only, and absent tokens would satisfy the API rule that every feature include tests.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx:127">
P2: The credential form is only reachable when there are no automations yet, so existing tasks without a browser context lose the ability to start the new connect flow. It would be safer to expose the same start-connect action anywhere `context.status === 'no-context'` can occur, not just in the empty state.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

async storeProfileCredentials(
@OrganizationId() organizationId: string,
@Param('profileId') profileId: string,
@Body() dto: StoreAuthProfileCredentialsDto,

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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.

P0: The new credentials request can persist the raw TOTP seed in the audit database. password is redacted by SENSITIVE_KEYS, but totpSeed is not, so this endpoint should add TOTP seed redaction (with a regression test) before accepting secrets through an audited mutation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-auth-profiles.controller.ts, line 129:

<comment>The new credentials request can persist the raw TOTP seed in the audit database. `password` is redacted by `SENSITIVE_KEYS`, but `totpSeed` is not, so this endpoint should add TOTP seed redaction (with a regression test) before accepting secrets through an audited mutation.</comment>

<file context>
@@ -112,6 +113,30 @@ export class BrowserAuthProfilesController {
+  async storeProfileCredentials(
+    @OrganizationId() organizationId: string,
+    @Param('profileId') profileId: string,
+    @Body() dto: StoreAuthProfileCredentialsDto,
+  ): Promise<BrowserAuthProfileResponseDto> {
+    return (await this.browserbaseService.storeAuthProfileCredentials({
</file context>
Fix with cubic

}): Promise<RuntimeCredentialMaterial | null> {
if (params.provider !== ONEPASSWORD_PROVIDER) return null;

const itemRef = params.externalItemRef?.trim();

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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: A caller with integration:create can point a profile at any op:// item the shared service account can read, allowing an automation to use credentials from another organization's vault when the reference is known. The reference should be derived from server-owned storage or validated against the profile's organization/connection before resolving it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/onepassword-credential-vault.adapter.ts, line 34:

<comment>A caller with `integration:create` can point a profile at any `op://` item the shared service account can read, allowing an automation to use credentials from another organization's vault when the reference is known. The reference should be derived from server-owned storage or validated against the profile's organization/connection before resolving it.</comment>

<file context>
@@ -0,0 +1,78 @@
+  }): Promise<RuntimeCredentialMaterial | null> {
+    if (params.provider !== ONEPASSWORD_PROVIDER) return null;
+
+    const itemRef = params.externalItemRef?.trim();
+    if (!itemRef) return null;
+
</file context>
Fix with cubic


if (totpSeed?.trim()) {
fields.push({
id: TOTP_FIELD_TITLE,

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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: Providing a TOTP seed does not create the documented 1Password Login OTP field because its ID is set to the display title. Keeping the title for secret references but using the built-in ID onetimepassword allows OTP generation and resolution to work reliably.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-credential-storage.service.ts, line 139:

<comment>Providing a TOTP seed does not create the documented 1Password Login OTP field because its ID is set to the display title. Keeping the title for secret references but using the built-in ID `onetimepassword` allows OTP generation and resolution to work reliably.</comment>

<file context>
@@ -0,0 +1,148 @@
+
+    if (totpSeed?.trim()) {
+      fields.push({
+        id: TOTP_FIELD_TITLE,
+        title: TOTP_FIELD_TITLE,
+        fieldType: ItemFieldType.Totp,
</file context>
Fix with cubic

@Param('profileId') profileId: string,
@Body() dto: StoreAuthProfileCredentialsDto,
): Promise<BrowserAuthProfileResponseDto> {
return (await this.browserbaseService.storeAuthProfileCredentials({

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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: Repeated saves through this endpoint create a new 1Password Login item while leaving the previous item—and its old password/TOTP secret—in the organization vault. Reuse the existing provider-owned item or remove the old item after a successful replacement so credential updates do not accumulate stale secrets.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-auth-profiles.controller.ts, line 131:

<comment>Repeated saves through this endpoint create a new 1Password Login item while leaving the previous item—and its old password/TOTP secret—in the organization vault. Reuse the existing provider-owned item or remove the old item after a successful replacement so credential updates do not accumulate stale secrets.</comment>

<file context>
@@ -112,6 +113,30 @@ export class BrowserAuthProfilesController {
+    @Param('profileId') profileId: string,
+    @Body() dto: StoreAuthProfileCredentialsDto,
+  ): Promise<BrowserAuthProfileResponseDto> {
+    return (await this.browserbaseService.storeAuthProfileCredentials({
+      organizationId,
+      profileId,
</file context>
Fix with cubic

if (!profile) {
throw new NotFoundException('Browser auth profile not found');
}
// Avoid a needless write when the profile is already verified.

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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: Successful automation runs on an already-verified profile do not refresh lastVerifiedAt, so the API can report the original verification time instead of the latest successful authentication. Persist the successful-run timestamp even when the status is already verified; only avoid a status-only write if no metadata needs updating.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-auth-profile.service.ts, line 214:

<comment>Successful automation runs on an already-verified profile do not refresh `lastVerifiedAt`, so the API can report the original verification time instead of the latest successful authentication. Persist the successful-run timestamp even when the status is already `verified`; only avoid a status-only write if no metadata needs updating.</comment>

<file context>
@@ -206,6 +206,24 @@ export class BrowserAuthProfileService {
+    if (!profile) {
+      throw new NotFoundException('Browser auth profile not found');
+    }
+    // Avoid a needless write when the profile is already verified.
+    if (profile.status === 'verified') return profile;
+
</file context>
Fix with cubic

return { profile: updated, auth };
}

async markVerified(input: { organizationId: string; profileId: string }) {

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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 verification transition is untested, so regressions in clearing blockedReason, recording lastVerifiedAt, or handling an already-verified profile can pass unnoticed. Adding focused service tests for the missing-profile, already-verified, and state-transition cases would cover this path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-auth-profile.service.ts, line 209:

<comment>The new verification transition is untested, so regressions in clearing `blockedReason`, recording `lastVerifiedAt`, or handling an already-verified profile can pass unnoticed. Adding focused service tests for the missing-profile, already-verified, and state-transition cases would cover this path.</comment>

<file context>
@@ -206,6 +206,24 @@ export class BrowserAuthProfileService {
     return { profile: updated, auth };
   }
 
+  async markVerified(input: { organizationId: string; profileId: string }) {
+    const profile = await this.getProfile(input);
+    if (!profile) {
</file context>
Fix with cubic

(await checkAuth(activeStagehand)).isLoggedIn,
log: (message) => log('auth', message),
});
page = relogin.page ?? page;

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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: relogin.page is never used after this assignment: failure returns immediately, and success overwrites page during evidence-page resolution. Removing this dead assignment keeps the re-auth flow clearer and avoids implying that the returned page controls the subsequent action.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-evidence-execution.ts, line 97:

<comment>`relogin.page` is never used after this assignment: failure returns immediately, and success overwrites `page` during evidence-page resolution. Removing this dead assignment keeps the re-auth flow clearer and avoids implying that the returned page controls the subsequent action.</comment>

<file context>
@@ -74,12 +81,38 @@ export async function executeBrowserEvidence({
+          (await checkAuth(activeStagehand)).isLoggedIn,
+        log: (message) => log('auth', message),
+      });
+      page = relogin.page ?? page;
+
+      if (!relogin.isLoggedIn) {
</file context>
Fix with cubic

);
log('auth', classified.userFacing);
return toExecutionFailure({ classified, logs });
const relogin = await reloginWithStoredCredentials({

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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 authentication branch can regress without detecting whether executeBrowserEvidence resumes actions after successful recovery or returns the expected needs_reauth result after each failure mode. A focused execution-level test covering those branches would protect the integration between checkAuth, the vault-backed relogin helper, and result classification.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-evidence-execution.ts, line 88:

<comment>The new authentication branch can regress without detecting whether `executeBrowserEvidence` resumes actions after successful recovery or returns the expected `needs_reauth` result after each failure mode. A focused execution-level test covering those branches would protect the integration between `checkAuth`, the vault-backed relogin helper, and result classification.</comment>

<file context>
@@ -74,12 +81,38 @@ export async function executeBrowserEvidence({
       );
-      log('auth', classified.userFacing);
-      return toExecutionFailure({ classified, logs });
+      const relogin = await reloginWithStoredCredentials({
+        stagehand: activeStagehand,
+        sessions,
</file context>
Fix with cubic

// own, so let a needs_reauth profile attempt the run instead of blocking it.
// Profiles that are blocked or never verified still require a human.
const canAutoRelogin =
profile.status === 'needs_reauth' && Boolean(profile.vaultProvider);

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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 auto-relogin and success-to-verified transitions are untested, so regressions in credential gating or profile-state recovery can silently reach scheduled runs. Adding execution-service tests for usable versus incomplete vault metadata, successful recovery, and a failed result preserving needs_reauth would make this behavior verifiable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-automation-execution.service.ts, line 144:

<comment>The new auto-relogin and success-to-verified transitions are untested, so regressions in credential gating or profile-state recovery can silently reach scheduled runs. Adding execution-service tests for usable versus incomplete vault metadata, successful recovery, and a failed result preserving `needs_reauth` would make this behavior verifiable.</comment>

<file context>
@@ -137,7 +137,12 @@ export class BrowserAutomationExecutionService {
+    // own, so let a needs_reauth profile attempt the run instead of blocking it.
+    // Profiles that are blocked or never verified still require a human.
+    const canAutoRelogin =
+      profile.status === 'needs_reauth' && Boolean(profile.vaultProvider);
+    if (profile.status !== 'verified' && !canAutoRelogin) {
       const result = this.profileBlockedResult(profile.status);
</file context>
Fix with cubic

@@ -0,0 +1,148 @@
import {

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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 credential-storage workflow has no unit coverage for tenant scoping, vault reuse/creation, TOTP field construction, external failures, or profile-reference persistence. Adding a colocated service spec would catch regressions in this secrets-handling path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-credential-storage.service.ts, line 38:

<comment>The new credential-storage workflow has no unit coverage for tenant scoping, vault reuse/creation, TOTP field construction, external failures, or profile-reference persistence. Adding a colocated service spec would catch regressions in this secrets-handling path.</comment>

<file context>
@@ -0,0 +1,148 @@
+ * item. Comp never persists the raw secrets itself — only the `op://` reference.
+ */
+@Injectable()
+export class BrowserCredentialStorageService {
+  private readonly logger = new Logger(BrowserCredentialStorageService.name);
+
</file context>
Fix with cubic

Replaces the bare "enter a URL" prompt with the three-step explainer
(01 Connect a login / 02 Describe what to capture / 03 Evidence on schedule),
a single "Connect a vendor login" action, and the 1Password reassurance line.
The URL is now collected in the connect credentials form rather than here.

Part of the Browser Automations redesign (first-time setup).

@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.

1 issue found across 2 files (changes from recent commits).

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="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/BrowserEmptyStates.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/BrowserEmptyStates.tsx:49">
P2: Users without `integration:create` can still see and open this connection flow, then receive a failed request when they submit it because the API rejects `/v1/browserbase/profiles/resolve`. The empty state could use the same permission check as `BrowserAutomationsList` and hide or disable this CTA for users who cannot create integrations.</violation>
</file>

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

Fix all with cubic | Re-trigger cubic

</div>

<div className="mt-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<Button onClick={onConnect} loading={isStartingAuth} disabled={isStartingAuth}>

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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: Users without integration:create can still see and open this connection flow, then receive a failed request when they submit it because the API rejects /v1/browserbase/profiles/resolve. The empty state could use the same permission check as BrowserAutomationsList and hide or disable this CTA for users who cannot create integrations.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/BrowserEmptyStates.tsx, line 49:

<comment>Users without `integration:create` can still see and open this connection flow, then receive a failed request when they submit it because the API rejects `/v1/browserbase/profiles/resolve`. The empty state could use the same permission check as `BrowserAutomationsList` and hide or disable this CTA for users who cannot create integrations.</comment>

<file context>
@@ -1,63 +1,58 @@
-          </div>
+
+      <div className="mt-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
+        <Button onClick={onConnect} loading={isStartingAuth} disabled={isStartingAuth}>
+          Connect a vendor login
+          <ArrowRight size={14} />
</file context>
Fix with cubic

Switches the first-time setup screen to the designer's 2c variant — a split
card with the pitch and "Connect a Vendor Login" action on the left and a quiet
"How it works" rail (01/02/03) on the right. Stacks to one column on mobile.
Adds a login-analysis backend: POST /v1/browserbase/analyze-login opens the
vendor sign-in page in a throwaway cloud browser, detects the supported login
methods (password / SSO / passkey, identifier type, extra fields), and returns a
recommendation (ready / works-with-check-ins / manual). Reads a public page only
— no credentials — and always degrades to a manual fallback if it can't be read.

Foundation for the smart-connect flow. Unit tests for the recommendation logic
and the analyzer service.

@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.

6 issues found across 9 files (changes from recent commits).

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="apps/api/src/browserbase/browserbase.service.ts">

<violation number="1" location="apps/api/src/browserbase/browserbase.service.ts:40">
P3: The new login-analysis API path is not covered at the `BrowserbaseService` boundary, so a broken or missing delegation could pass the current tests even though the analyzer itself is tested. A focused service test (and controller request test) would cover the new endpoint wiring.</violation>
</file>

<file name="apps/api/src/browserbase/dto/browserbase.dto.ts">

<violation number="1" location="apps/api/src/browserbase/dto/browserbase.dto.ts:91">
P3: The generated API contract permits arbitrary method names and generated clients lose the supported-value set. Document this as an enum array to match `LoginMethod` and the values returned by the analyzer.</violation>

<violation number="2" location="apps/api/src/browserbase/dto/browserbase.dto.ts:97">
P3: The `extraFields` property in `LoginAnalysisResponseDto` uses `@ApiProperty({ type: [Object] })`, which renders the nested structure as a generic array of plain objects in the Swagger/OpenAPI spec. Consumers won't see the `{ label: string }` shape. Define a small DTO class (e.g. `ExtraFieldDto`) with a single `@ApiProperty()` on `label` and reference it via `@ApiProperty({ type: [ExtraFieldDto] })` so the generated spec is self-documenting.</violation>
</file>

<file name="apps/api/src/browserbase/browser-login-analysis.ts">

<violation number="1" location="apps/api/src/browserbase/browser-login-analysis.ts:84">
P2: Sites with password forms but SMS, email-code, push approval, or another unsupported MFA method are reported as `ready` even though scheduled re-login will fall back to `needs_reauth`. Since this first-page analysis cannot identify the MFA method, the recommendation should remain check-in/manual until live sign-in proves unattended re-login, or the response should avoid claiming `READY`.</violation>

<violation number="2" location="apps/api/src/browserbase/browser-login-analysis.ts:84">
P2: A reachable password page with a required workspace/company/subdomain field is also labeled `ready`, but that field is not captured or stored for re-login. Including `extraFields.length === 0` in the unattended path, with an appropriate check-in/manual recommendation otherwise, would prevent this false-ready result.</violation>
</file>

<file name="apps/api/src/browserbase/browser-login-analyzer.service.ts">

<violation number="1" location="apps/api/src/browserbase/browser-login-analyzer.service.ts:34">
P2: Every login analysis leaks a persistent Browserbase context: `createSessionWithContext` saves browser data with `persist: true`, while `finally` closes only the session and drops the fresh `contextId`. Repeated analyses can retain vendor cookies/local storage indefinitely and accumulate orphaned contexts; an ephemeral session or explicit context deletion would avoid this.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

if (detection.hasPasskey) detectedMethods.push('passkey');

let recommendation: LoginAnalysis['recommendation'] = MANUAL;
if (detection.reachable && detection.hasPasswordField) {

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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: A reachable password page with a required workspace/company/subdomain field is also labeled ready, but that field is not captured or stored for re-login. Including extraFields.length === 0 in the unattended path, with an appropriate check-in/manual recommendation otherwise, would prevent this false-ready result.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-login-analysis.ts, line 84:

<comment>A reachable password page with a required workspace/company/subdomain field is also labeled `ready`, but that field is not captured or stored for re-login. Including `extraFields.length === 0` in the unattended path, with an appropriate check-in/manual recommendation otherwise, would prevent this false-ready result.</comment>

<file context>
@@ -0,0 +1,114 @@
+  if (detection.hasPasskey) detectedMethods.push('passkey');
+
+  let recommendation: LoginAnalysis['recommendation'] = MANUAL;
+  if (detection.reachable && detection.hasPasswordField) {
+    // A password form means we can capture credentials + an authenticator key
+    // and run fully unattended (the 2FA specifics surface during sign-in).
</file context>
Fix with cubic

if (detection.hasPasskey) detectedMethods.push('passkey');

let recommendation: LoginAnalysis['recommendation'] = MANUAL;
if (detection.reachable && detection.hasPasswordField) {

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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: Sites with password forms but SMS, email-code, push approval, or another unsupported MFA method are reported as ready even though scheduled re-login will fall back to needs_reauth. Since this first-page analysis cannot identify the MFA method, the recommendation should remain check-in/manual until live sign-in proves unattended re-login, or the response should avoid claiming READY.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-login-analysis.ts, line 84:

<comment>Sites with password forms but SMS, email-code, push approval, or another unsupported MFA method are reported as `ready` even though scheduled re-login will fall back to `needs_reauth`. Since this first-page analysis cannot identify the MFA method, the recommendation should remain check-in/manual until live sign-in proves unattended re-login, or the response should avoid claiming `READY`.</comment>

<file context>
@@ -0,0 +1,114 @@
+  if (detection.hasPasskey) detectedMethods.push('passkey');
+
+  let recommendation: LoginAnalysis['recommendation'] = MANUAL;
+  if (detection.reachable && detection.hasPasswordField) {
+    // A password form means we can capture credentials + an authenticator key
+    // and run fully unattended (the 2FA specifics surface during sign-in).
</file context>
Fix with cubic

) {}

async analyzeLogin(url: string): Promise<LoginAnalysis> {
const contextId = await this.sessions.createBrowserbaseContext();

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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: Every login analysis leaks a persistent Browserbase context: createSessionWithContext saves browser data with persist: true, while finally closes only the session and drops the fresh contextId. Repeated analyses can retain vendor cookies/local storage indefinitely and accumulate orphaned contexts; an ephemeral session or explicit context deletion would avoid this.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-login-analyzer.service.ts, line 34:

<comment>Every login analysis leaks a persistent Browserbase context: `createSessionWithContext` saves browser data with `persist: true`, while `finally` closes only the session and drops the fresh `contextId`. Repeated analyses can retain vendor cookies/local storage indefinitely and accumulate orphaned contexts; an ephemeral session or explicit context deletion would avoid this.</comment>

<file context>
@@ -0,0 +1,66 @@
+  ) {}
+
+  async analyzeLogin(url: string): Promise<LoginAnalysis> {
+    const contextId = await this.sessions.createBrowserbaseContext();
+    const { sessionId } =
+      await this.sessions.createSessionWithContext(contextId);
</file context>
Fix with cubic

Comment thread apps/api/src/browserbase/browser-login-analyzer.service.ts Outdated
@ApiProperty()
reachable: boolean;

@ApiProperty({ type: [String] })

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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 generated API contract permits arbitrary method names and generated clients lose the supported-value set. Document this as an enum array to match LoginMethod and the values returned by the analyzer.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/dto/browserbase.dto.ts, line 91:

<comment>The generated API contract permits arbitrary method names and generated clients lose the supported-value set. Document this as an enum array to match `LoginMethod` and the values returned by the analyzer.</comment>

<file context>
@@ -62,10 +62,51 @@ export class AuthStatusResponseDto {
+  @ApiProperty()
+  reachable: boolean;
+
+  @ApiProperty({ type: [String] })
+  detectedMethods: string[];
+
</file context>
Suggested change
@ApiProperty({ type: [String] })
@ApiProperty({ enum: ['password', 'sso', 'passkey'], isArray: true })
Fix with cubic

) {}

async analyzeLogin(url: string) {
return this.loginAnalyzer.analyzeLogin(url);

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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 login-analysis API path is not covered at the BrowserbaseService boundary, so a broken or missing delegation could pass the current tests even though the analyzer itself is tested. A focused service test (and controller request test) would cover the new endpoint wiring.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browserbase.service.ts, line 40:

<comment>The new login-analysis API path is not covered at the `BrowserbaseService` boundary, so a broken or missing delegation could pass the current tests even though the analyzer itself is tested. A focused service test (and controller request test) would cover the new endpoint wiring.</comment>

<file context>
@@ -24,11 +25,21 @@ export class BrowserbaseService {
   ) {}
 
+  async analyzeLogin(url: string) {
+    return this.loginAnalyzer.analyzeLogin(url);
+  }
+
</file context>
Fix with cubic

@ApiProperty({ enum: ['email', 'username', 'either', 'unknown'] })
identifierType: string;

@ApiProperty({ type: [Object] })

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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 extraFields property in LoginAnalysisResponseDto uses @ApiProperty({ type: [Object] }), which renders the nested structure as a generic array of plain objects in the Swagger/OpenAPI spec. Consumers won't see the { label: string } shape. Define a small DTO class (e.g. ExtraFieldDto) with a single @ApiProperty() on label and reference it via @ApiProperty({ type: [ExtraFieldDto] }) so the generated spec is self-documenting.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/dto/browserbase.dto.ts, line 97:

<comment>The `extraFields` property in `LoginAnalysisResponseDto` uses `@ApiProperty({ type: [Object] })`, which renders the nested structure as a generic array of plain objects in the Swagger/OpenAPI spec. Consumers won't see the `{ label: string }` shape. Define a small DTO class (e.g. `ExtraFieldDto`) with a single `@ApiProperty()` on `label` and reference it via `@ApiProperty({ type: [ExtraFieldDto] })` so the generated spec is self-documenting.</comment>

<file context>
@@ -62,10 +62,51 @@ export class AuthStatusResponseDto {
+  @ApiProperty({ enum: ['email', 'username', 'either', 'unknown'] })
+  identifierType: string;
+
+  @ApiProperty({ type: [Object] })
+  extraFields: { label: string }[];
+
</file context>
Fix with cubic

Replaces the connect flow with the "Browser-First Split" (1C) design: a stepper
rail plus a browser stage that opens the vendor URL, runs AI login-method
detection (/analyze-login), shows a recommendation (ready / works-with-check-ins
/ manual), opens the live sign-in, then captures the reusable credentials
(username, password, authenticator setup key) after verification. Removes the
superseded credentials-first form.

Adds tests for the capture form. The SMS-only -> "enable an authenticator app"
recommendation is deferred to the live sign-in step (2FA method isn't visible
before sign-in).

@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.

3 issues found across 10 files (changes from recent commits).

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="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/types.ts">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/types.ts:67">
P3: Invalid login methods and identifier types can flow through the UI without a compile-time error because this response type widens the API's finite value sets to `string`. Mirroring the API unions here would keep consumers type-checked when the login contract changes.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectCaptureForm.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectCaptureForm.tsx:43">
P3: Validation failures are only visually connected to the fields, so screen-reader users may not learn which required credential is invalid. Associate each input with its error via `aria-invalid` and `aria-describedby`, and expose the error as an alert.</violation>

<violation number="2" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectCaptureForm.tsx:62">
P2: The authenticator setup key is rendered as plaintext, allowing anyone viewing this capture screen to copy the MFA seed and generate future codes. Treat this value like the password with a masked input, optionally adding an explicit reveal control.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

<Label htmlFor="capture-totp">Authenticator setup key</Label>
<span className="text-xs text-muted-foreground">Optional — recommended</span>
</div>
<Input id="capture-totp" placeholder="e.g. JBSW Y3DP EHPK 3PXP" {...register('totpSeed')} />

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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 authenticator setup key is rendered as plaintext, allowing anyone viewing this capture screen to copy the MFA seed and generate future codes. Treat this value like the password with a masked input, optionally adding an explicit reveal control.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectCaptureForm.tsx, line 62:

<comment>The authenticator setup key is rendered as plaintext, allowing anyone viewing this capture screen to copy the MFA seed and generate future codes. Treat this value like the password with a masked input, optionally adding an explicit reveal control.</comment>

<file context>
@@ -0,0 +1,79 @@
+          <Label htmlFor="capture-totp">Authenticator setup key</Label>
+          <span className="text-xs text-muted-foreground">Optional — recommended</span>
+        </div>
+        <Input id="capture-totp" placeholder="e.g. JBSW Y3DP EHPK 3PXP" {...register('totpSeed')} />
+        <p className="text-xs text-muted-foreground leading-relaxed">
+          Shown when you set up the authenticator app (&ldquo;can&apos;t scan? enter this
</file context>
Fix with cubic

Comment on lines +67 to +68
detectedMethods: string[];
identifierType: string;

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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: Invalid login methods and identifier types can flow through the UI without a compile-time error because this response type widens the API's finite value sets to string. Mirroring the API unions here would keep consumers type-checked when the login contract changes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/types.ts, line 67:

<comment>Invalid login methods and identifier types can flow through the UI without a compile-time error because this response type widens the API's finite value sets to `string`. Mirroring the API unions here would keep consumers type-checked when the login contract changes.</comment>

<file context>
@@ -60,6 +60,20 @@ export interface BrowserLoginCredentials {
+
+export interface LoginAnalysis {
+  reachable: boolean;
+  detectedMethods: string[];
+  identifierType: string;
+  extraFields: { label: string }[];
</file context>
Suggested change
detectedMethods: string[];
identifierType: string;
detectedMethods: ('password' | 'sso' | 'passkey')[];
identifierType: 'email' | 'username' | 'either' | 'unknown';
Fix with cubic

Comment thread apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx Outdated

<div className="flex flex-col gap-2">
<Label htmlFor="capture-username">Username or email</Label>
<Input id="capture-username" autoComplete="off" {...register('username')} />

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

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: Validation failures are only visually connected to the fields, so screen-reader users may not learn which required credential is invalid. Associate each input with its error via aria-invalid and aria-describedby, and expose the error as an alert.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectCaptureForm.tsx, line 43:

<comment>Validation failures are only visually connected to the fields, so screen-reader users may not learn which required credential is invalid. Associate each input with its error via `aria-invalid` and `aria-describedby`, and expose the error as an alert.</comment>

<file context>
@@ -0,0 +1,79 @@
+
+      <div className="flex flex-col gap-2">
+        <Label htmlFor="capture-username">Username or email</Label>
+        <Input id="capture-username" autoComplete="off" {...register('username')} />
+        {errors.username?.message && (
+          <p className="text-xs text-destructive">{errors.username.message}</p>
</file context>
Fix with cubic

Rewrites the automations list to group automations under their vendor connection.
Each connection shows its status (Connected / Needs reconnect / Needs your action
/ Not connected) with a one-click Reconnect for the ones that need a human. Adds a
useBrowserProfiles hook to load connection statuses and wires reconnect through the
existing live sign-in.

@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.

2 issues found across 5 files (changes from recent commits).

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="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectLiveSignin.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectLiveSignin.tsx:181">
P3: Screen-reader users receive no announcement when the transient “Signed in / Saving the session…” state appears because the success overlay is a plain `div`. Mark the overlay as a live status so the completion feedback is announced when it mounts.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectVendorLoginFlow.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectVendorLoginFlow.tsx:189">
P2: An unmount during the 1.3-second success beat leaks the automated Browserbase session: clearing the timer prevents the callback containing `endSession()` from running, and the cleanup itself never closes the keep-alive session. The success transition would benefit from an unmount-safe session cleanup while retaining the delayed close for the normal path.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

endSession();
setStep('connected');
}, 1300);
return () => clearTimeout(timer);

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: An unmount during the 1.3-second success beat leaks the automated Browserbase session: clearing the timer prevents the callback containing endSession() from running, and the cleanup itself never closes the keep-alive session. The success transition would benefit from an unmount-safe session cleanup while retaining the delayed close for the normal path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectVendorLoginFlow.tsx, line 189:

<comment>An unmount during the 1.3-second success beat leaks the automated Browserbase session: clearing the timer prevents the callback containing `endSession()` from running, and the cleanup itself never closes the keep-alive session. The success transition would benefit from an unmount-safe session cleanup while retaining the delayed close for the normal path.</comment>

<file context>
@@ -173,11 +173,22 @@ export function ConnectVendorLoginFlow({
+      endSession();
+      setStep('connected');
+    }, 1300);
+    return () => clearTimeout(timer);
+  }, [step, endSession]);
+
</file context>
Fix with cubic

</div>

{success && (
<div

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: Screen-reader users receive no announcement when the transient “Signed in / Saving the session…” state appears because the success overlay is a plain div. Mark the overlay as a live status so the completion feedback is announced when it mounts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectLiveSignin.tsx, line 181:

<comment>Screen-reader users receive no announcement when the transient “Signed in / Saving the session…” state appears because the success overlay is a plain `div`. Mark the overlay as a live status so the completion feedback is announced when it mounts.</comment>

<file context>
@@ -173,6 +176,24 @@ export function ConnectLiveSignin({
       </div>
+
+      {success && (
+        <div
+          className="absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 backdrop-blur-sm animate-in fade-in-0 duration-300"
+          style={{ background: 'color-mix(in oklab, var(--background) 82%, transparent)' }}
</file context>
Suggested change
<div
<div role="status" aria-live="polite"
Fix with cubic

Surface the pass/fail check by default (drop the opt-in toggle and OPTIONAL
label) since evidence instructions usually want a verdict; leave it blank to
capture a screenshot only. Add clickable example chips under both the
instruction and check fields — a short label inserts a full example — shown
while the field is empty and hidden once the user starts typing.
…ctivity header

The composer resolved the connection from a stale default host, so a freshly
connected vendor could be tested against the wrong site's saved profile. Pass
the connected URL up from the connect flow so instructions bind to the vendor
the user actually connected. Also align the "Sign-in activity" header with the
browser chrome bar (same 34px row).
After signing in, the run navigated back to the entered URL before checking the
result — but some apps always show a login page at that URL, so a successful
sign-in read as "did not complete" and forced a needless re-auth. Verify on the
page the site lands on after login instead, and detect the signed-in state by
the absence of a sign-in prompt rather than an avatar/profile marker (many apps
have none), which was causing false negatives.
Give the live view a 16:9 frame matching the capture resolution so it fills the
panel width instead of leaving wide side gaps, and stays sharp (downscaled).

@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.

4 issues found across 4 files (changes from recent commits).

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="apps/api/src/browserbase/browser-credential-login.ts">

<violation number="1" location="apps/api/src/browserbase/browser-credential-login.ts:201">
P2: A login flow that opens a new tab can be reported as failed or continue from the pre-login page because `ensureActivePage()` does not select the page where the sign-in landed. Returning the login page/current navigation target (and bringing it to the front before verification) would keep verification and evidence on the authenticated page.</violation>
</file>

<file name="apps/api/src/browserbase/browser-evidence-execution.ts">

<violation number="1" location="apps/api/src/browserbase/browser-evidence-execution.ts:249">
P2: Authenticated security and account pages can contain password inputs without being sign-in screens. Because this prompt treats a password field alone as evidence of logout, runs targeting those pages can attempt re-login and end in a false `needs_reauth` result; the check should require the field to be part of a clearly identified authentication flow.</violation>

<violation number="2" location="apps/api/src/browserbase/browser-evidence-execution.ts:249">
P2: Two-step sign-in pages commonly show only an email/username field and a `Next` button before revealing the password, but this prompt does not classify that state as logged out. The run can therefore skip credential recovery and execute the evidence instruction on the sign-in page; username-only, `Next`/`Continue`, passkey, and verification-code states should also count as authentication prompts.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionTestPanel.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionTestPanel.tsx:116">
P3: On short viewports or sufficiently wide panels, `max-h-[70vh]` clamps the height without reducing the `w-full` width, overriding the intended 16:9 sizing and potentially reintroducing gaps or cropping in the live view. Preserving the ratio would require constraining the width to the corresponding 70vh maximum (and applying the same fix to the loading placeholder), or removing the height cap.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

// sign-in even though it succeeded. The evidence instruction navigates from
// wherever we land.
await delay(1500);
return sessions.ensureActivePage(stagehand);

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: A login flow that opens a new tab can be reported as failed or continue from the pre-login page because ensureActivePage() does not select the page where the sign-in landed. Returning the login page/current navigation target (and bringing it to the front before verification) would keep verification and evidence on the authenticated page.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-credential-login.ts, line 201:

<comment>A login flow that opens a new tab can be reported as failed or continue from the pre-login page because `ensureActivePage()` does not select the page where the sign-in landed. Returning the login page/current navigation target (and bringing it to the front before verification) would keep verification and evidence on the authenticated page.</comment>

<file context>
@@ -186,25 +184,21 @@ async function runLoginAttempt({
+  // wherever we land.
   await delay(1500);
-  return page;
+  return sessions.ensureActivePage(stagehand);
 }
 
</file context>
Fix with cubic

// page as logged in unless a sign-in prompt is clearly the ask.
return stagehand.extract(
'Check if the user is logged in to this website. Look for a user avatar, profile menu, account dropdown, or login/sign-in buttons. Return true if logged in, false if you see login buttons or a login form.',
'Is this page asking the user to sign in? Look for a visible login prompt: a password field, username/email + password fields, an SSO/"Continue with" screen, or a page whose main call to action is Sign in / Log in. Return isLoggedIn=false ONLY if such a sign-in prompt is clearly present. For any ordinary application or content page with no sign-in prompt, return isLoggedIn=true.',

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: Authenticated security and account pages can contain password inputs without being sign-in screens. Because this prompt treats a password field alone as evidence of logout, runs targeting those pages can attempt re-login and end in a false needs_reauth result; the check should require the field to be part of a clearly identified authentication flow.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-evidence-execution.ts, line 249:

<comment>Authenticated security and account pages can contain password inputs without being sign-in screens. Because this prompt treats a password field alone as evidence of logout, runs targeting those pages can attempt re-login and end in a false `needs_reauth` result; the check should require the field to be part of a clearly identified authentication flow.</comment>

<file context>
@@ -241,8 +241,12 @@ async function checkAuth(
+  // page as logged in unless a sign-in prompt is clearly the ask.
   return stagehand.extract(
-    'Check if the user is logged in to this website. Look for a user avatar, profile menu, account dropdown, or login/sign-in buttons. Return true if logged in, false if you see login buttons or a login form.',
+    'Is this page asking the user to sign in? Look for a visible login prompt: a password field, username/email + password fields, an SSO/"Continue with" screen, or a page whose main call to action is Sign in / Log in. Return isLoggedIn=false ONLY if such a sign-in prompt is clearly present. For any ordinary application or content page with no sign-in prompt, return isLoggedIn=true.',
     loginSchema,
   );
</file context>
Fix with cubic

// page as logged in unless a sign-in prompt is clearly the ask.
return stagehand.extract(
'Check if the user is logged in to this website. Look for a user avatar, profile menu, account dropdown, or login/sign-in buttons. Return true if logged in, false if you see login buttons or a login form.',
'Is this page asking the user to sign in? Look for a visible login prompt: a password field, username/email + password fields, an SSO/"Continue with" screen, or a page whose main call to action is Sign in / Log in. Return isLoggedIn=false ONLY if such a sign-in prompt is clearly present. For any ordinary application or content page with no sign-in prompt, return isLoggedIn=true.',

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: Two-step sign-in pages commonly show only an email/username field and a Next button before revealing the password, but this prompt does not classify that state as logged out. The run can therefore skip credential recovery and execute the evidence instruction on the sign-in page; username-only, Next/Continue, passkey, and verification-code states should also count as authentication prompts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-evidence-execution.ts, line 249:

<comment>Two-step sign-in pages commonly show only an email/username field and a `Next` button before revealing the password, but this prompt does not classify that state as logged out. The run can therefore skip credential recovery and execute the evidence instruction on the sign-in page; username-only, `Next`/`Continue`, passkey, and verification-code states should also count as authentication prompts.</comment>

<file context>
@@ -241,8 +241,12 @@ async function checkAuth(
+  // page as logged in unless a sign-in prompt is clearly the ask.
   return stagehand.extract(
-    'Check if the user is logged in to this website. Look for a user avatar, profile menu, account dropdown, or login/sign-in buttons. Return true if logged in, false if you see login buttons or a login form.',
+    'Is this page asking the user to sign in? Look for a visible login prompt: a password field, username/email + password fields, an SSO/"Continue with" screen, or a page whose main call to action is Sign in / Log in. Return isLoggedIn=false ONLY if such a sign-in prompt is clearly present. For any ordinary application or content page with no sign-in prompt, return isLoggedIn=true.',
     loginSchema,
   );
</file context>
Suggested change
'Is this page asking the user to sign in? Look for a visible login prompt: a password field, username/email + password fields, an SSO/"Continue with" screen, or a page whose main call to action is Sign in / Log in. Return isLoggedIn=false ONLY if such a sign-in prompt is clearly present. For any ordinary application or content page with no sign-in prompt, return isLoggedIn=true.',
'Is this page asking the user to sign in? Return isLoggedIn=false only for a clearly identified authentication flow, such as a username/email-only step with Next or Continue, a username/email or password form, SSO/passkey/one-time-code screen, magic-link prompt, or primary Sign in / Log in action. Do not treat ordinary account, security, or change-password forms as sign-in; otherwise return isLoggedIn=true.',
Fix with cubic

title="Test run live view"
// 16:9 matches the 1920x1080 capture, so the view fills the width
// without side gaps and stays crisp (downscaled, not upscaled).
className="aspect-video max-h-[70vh] w-full"

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: On short viewports or sufficiently wide panels, max-h-[70vh] clamps the height without reducing the w-full width, overriding the intended 16:9 sizing and potentially reintroducing gaps or cropping in the live view. Preserving the ratio would require constraining the width to the corresponding 70vh maximum (and applying the same fix to the loading placeholder), or removing the height cap.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionTestPanel.tsx, line 116:

<comment>On short viewports or sufficiently wide panels, `max-h-[70vh]` clamps the height without reducing the `w-full` width, overriding the intended 16:9 sizing and potentially reintroducing gaps or cropping in the live view. Preserving the ratio would require constraining the width to the corresponding 70vh maximum (and applying the same fix to the loading placeholder), or removing the height cap.</comment>

<file context>
@@ -111,11 +111,13 @@ export function InstructionTestPanel({
-              className="h-[260px] w-full"
+              // 16:9 matches the 1920x1080 capture, so the view fills the width
+              // without side gaps and stays crisp (downscaled, not upscaled).
+              className="aspect-video max-h-[70vh] w-full"
               sandbox="allow-same-origin allow-scripts"
             />
</file context>
Fix with cubic

The step fired whenever the first page looked like a sign-in prompt — including
sites that always show login at the root, where nothing had expired. Reword it
to state plainly that we're not signed in on this page and are signing in.
Show the connection as its hostname with an avatar and a status-driven dot
(green verified / amber needs-reconnect / grey unverified) instead of the raw
profile name with an always-green dot. Frame the check as "PASS when <your
condition> / FAIL otherwise", so the pass/fail logic reads at a glance without a
redundant second condition.

@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.

1 issue found across 5 files (changes from recent commits).

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="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionComposer.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionComposer.tsx:234">
P3: Connection status is communicated only by the colored dot, so users cannot reliably distinguish connected, reconnect-required, blocked, and unconnected states without color perception or visual inspection. Adding a visible or visually hidden status label and marking the dot decorative would make the new status signal accessible.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

{connection.hostname}
<span
className="h-1.5 w-1.5 rounded-full"
style={{ background: statusDotColor(connection.status) }}

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: Connection status is communicated only by the colored dot, so users cannot reliably distinguish connected, reconnect-required, blocked, and unconnected states without color perception or visual inspection. Adding a visible or visually hidden status label and marking the dot decorative would make the new status signal accessible.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionComposer.tsx, line 234:

<comment>Connection status is communicated only by the colored dot, so users cannot reliably distinguish connected, reconnect-required, blocked, and unconnected states without color perception or visual inspection. Adding a visible or visually hidden status label and marking the dot decorative would make the new status signal accessible.</comment>

<file context>
@@ -214,10 +226,13 @@ export function InstructionComposer({
+            {connection.hostname}
+            <span
+              className="h-1.5 w-1.5 rounded-full"
+              style={{ background: statusDotColor(connection.status) }}
+            />
           </span>
</file context>
Fix with cubic

Sign-in alone can eat ~40s, so a 4-minute cap cut multi-step lookups off
mid-run. Raise the budget and tell the agent to find its own way and read
what's already on screen instead of over-navigating — the common cause of
hitting the limit.
Line the instruction field up with the top of the live browser panel. Explain
that the AI finds its own way (no exact steps needed) and what the advanced
start-page override is for, and surface a clear, actionable message when a test
run times out.
Keep navigation screenshot-based but make the computer-use model configurable
via env (Claude or OpenAI, no per-site tuning) and default to the newest Claude
our Stagehand supports. Give the agent more steps and a generous time budget so
it can recover from a wrong turn on a complex site, and tell it to verify the
page matches the request and fix a wrong item before finishing. The page-reading
model is a separate env knob too.
Use openai/computer-use-preview for screenshot-based navigation by default —
it's purpose-built for GUI agents — with a safe fallback to Claude when no
OpenAI key is set. Page reading / verdicts stay on a general model.
If the primary computer-use model is unavailable mid-run (preview access, rate
limits, upstream errors), retry navigation once on a Claude computer-use model
instead of failing the run. Use claude-sonnet-4-6 for the fallback — sonnet-5
isn't a computer-use model Stagehand supports.
Switch the default computer-use model to openai/gpt-5.6-terra — the balanced
tier of OpenAI's newest family, strong on browser automation at about half
Sol's cost. Still env-swappable (e.g. gpt-5.6-sol for max accuracy) with the
Claude fallback intact.

@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.

4 issues found across 6 files (changes from recent commits).

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="apps/api/src/browserbase/browserbase-session.service.ts">

<violation number="1" location="apps/api/src/browserbase/browserbase-session.service.ts:23">
P2: Setting `BROWSERBASE_STAGEHAND_MODEL` to an OpenAI model makes Stagehand `extract()`/`act()` calls fail because `createStagehand()` always supplies `ANTHROPIC_API_KEY` as the explicit key. Resolving the key from the model provider (or validating that this override is Anthropic-only) would make the advertised model configurability work.</violation>
</file>

<file name="apps/api/.env.example">

<violation number="1" location="apps/api/.env.example:62">
P2: When the API is launched through the root `turbo dev`, these documented model overrides cannot be supplied through the process environment because `turbo.json` does not allowlist either new variable in strict mode. Adding both names to `turbo.json`'s `globalEnv` would ensure the values reach the code that reads them.</violation>
</file>

<file name="apps/api/src/browserbase/browser-evidence-execution.ts">

<violation number="1" location="apps/api/src/browserbase/browser-evidence-execution.ts:26">
P1: The default navigation model is not supported by the Stagehand CUA model allowlist, so the new OpenAI default can never execute as the primary model. Using an allowlisted CUA model such as `openai/gpt-5.5` (or upgrading/verifying Stagehand support before selecting this ID) keeps the default path functional.</violation>

<violation number="2" location="apps/api/src/browserbase/browser-evidence-execution.ts:56">
P2: Configuring a non-OpenAI, non-Anthropic `BROWSERBASE_CUA_MODEL` can fail authentication because this branch always passes `ANTHROPIC_API_KEY` as its explicit key. Provider-specific key selection (or leaving the key unset so Stagehand resolves it from the configured provider) would make the advertised model override work across supported providers.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

// OpenAI's newest family — strong browser-automation accuracy at roughly half
// Sol's cost — a good fit for a multi-step agent. Configurable via env for A/B
// tests (e.g. openai/gpt-5.6-sol for max accuracy), with no per-site tuning.
const DEFAULT_CUA_MODEL = 'openai/gpt-5.6-terra';

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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 default navigation model is not supported by the Stagehand CUA model allowlist, so the new OpenAI default can never execute as the primary model. Using an allowlisted CUA model such as openai/gpt-5.5 (or upgrading/verifying Stagehand support before selecting this ID) keeps the default path functional.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-evidence-execution.ts, line 26:

<comment>The default navigation model is not supported by the Stagehand CUA model allowlist, so the new OpenAI default can never execute as the primary model. Using an allowlisted CUA model such as `openai/gpt-5.5` (or upgrading/verifying Stagehand support before selecting this ID) keeps the default path functional.</comment>

<file context>
@@ -19,7 +19,64 @@ import { reloginWithStoredCredentials } from './browser-credential-login';
+// OpenAI's newest family — strong browser-automation accuracy at roughly half
+// Sol's cost — a good fit for a multi-step agent. Configurable via env for A/B
+// tests (e.g. openai/gpt-5.6-sol for max accuracy), with no per-site tuning.
+const DEFAULT_CUA_MODEL = 'openai/gpt-5.6-terra';
+// Claude fallback used when the primary model is unavailable (missing OpenAI key,
+// preview access, rate limits, upstream errors). Must be a computer-use-capable
</file context>
Suggested change
const DEFAULT_CUA_MODEL = 'openai/gpt-5.6-terra';
const DEFAULT_CUA_MODEL = 'openai/gpt-5.5';
Fix with cubic

// Model behind extract()/act() (reading pages, verdicts, form fills). Separate
// from the navigation (CUA) model and configurable via env; default unchanged.
const STAGEHAND_MODEL =
process.env.BROWSERBASE_STAGEHAND_MODEL || 'anthropic/claude-sonnet-4-6';

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: Setting BROWSERBASE_STAGEHAND_MODEL to an OpenAI model makes Stagehand extract()/act() calls fail because createStagehand() always supplies ANTHROPIC_API_KEY as the explicit key. Resolving the key from the model provider (or validating that this override is Anthropic-only) would make the advertised model configurability work.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browserbase-session.service.ts, line 23:

<comment>Setting `BROWSERBASE_STAGEHAND_MODEL` to an OpenAI model makes Stagehand `extract()`/`act()` calls fail because `createStagehand()` always supplies `ANTHROPIC_API_KEY` as the explicit key. Resolving the key from the model provider (or validating that this override is Anthropic-only) would make the advertised model configurability work.</comment>

<file context>
@@ -17,7 +17,10 @@ type Stagehand = import('@browserbasehq/stagehand').Stagehand;
+// Model behind extract()/act() (reading pages, verdicts, form fills). Separate
+// from the navigation (CUA) model and configurable via env; default unchanged.
+const STAGEHAND_MODEL =
+  process.env.BROWSERBASE_STAGEHAND_MODEL || 'anthropic/claude-sonnet-4-6';
 const BROWSERBASE_API_MAX_ATTEMPTS = 3;
 const BROWSERBASE_RETRY_DELAYS_MS = [250, 750];
</file context>
Fix with cubic

Comment thread apps/api/.env.example
# computer-use model to use Claude instead.
# BROWSERBASE_STAGEHAND_MODEL: model behind page reading / pass-fail verdicts —
# keep this a general model (Claude), NOT a computer-use model.
BROWSERBASE_CUA_MODEL=

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: When the API is launched through the root turbo dev, these documented model overrides cannot be supplied through the process environment because turbo.json does not allowlist either new variable in strict mode. Adding both names to turbo.json's globalEnv would ensure the values reach the code that reads them.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/.env.example, line 62:

<comment>When the API is launched through the root `turbo dev`, these documented model overrides cannot be supplied through the process environment because `turbo.json` does not allowlist either new variable in strict mode. Adding both names to `turbo.json`'s `globalEnv` would ensure the values reach the code that reads them.</comment>

<file context>
@@ -52,6 +52,15 @@ GROQ_API_KEY=
+#   computer-use model to use Claude instead.
+# BROWSERBASE_STAGEHAND_MODEL: model behind page reading / pass-fail verdicts —
+#   keep this a general model (Claude), NOT a computer-use model.
+BROWSERBASE_CUA_MODEL=
+BROWSERBASE_STAGEHAND_MODEL=
 
</file context>
Fix with cubic

}
return {
modelName: requested,
apiKey: requested.startsWith('openai/')

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: Configuring a non-OpenAI, non-Anthropic BROWSERBASE_CUA_MODEL can fail authentication because this branch always passes ANTHROPIC_API_KEY as its explicit key. Provider-specific key selection (or leaving the key unset so Stagehand resolves it from the configured provider) would make the advertised model override work across supported providers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-evidence-execution.ts, line 56:

<comment>Configuring a non-OpenAI, non-Anthropic `BROWSERBASE_CUA_MODEL` can fail authentication because this branch always passes `ANTHROPIC_API_KEY` as its explicit key. Provider-specific key selection (or leaving the key unset so Stagehand resolves it from the configured provider) would make the advertised model override work across supported providers.</comment>

<file context>
@@ -19,7 +19,64 @@ import { reloginWithStoredCredentials } from './browser-credential-login';
+  }
+  return {
+    modelName: requested,
+    apiKey: requested.startsWith('openai/')
+      ? process.env.OPENAI_API_KEY
+      : process.env.ANTHROPIC_API_KEY,
</file context>
Fix with cubic

Two evidence-quality fixes: tell the agent to open the specific item an
instruction names so the screenshot shows just that (not a whole list), and
pass the instruction into the evaluator so a pass/fail check is judged about the
intended target — not any matching value that happens to appear elsewhere on the
page (which let an unrelated product's price satisfy the check).
The left field label and the right "Test run" header had different heights (the
header carries a status pill), so the textarea and the browser view didn't line
up. Fix both headers to the same height so they share one baseline.
The AI is meant to find its own way, so the composer no longer asks for a start
page — instructions always run from the connection. Removes the advanced
override and its field.

@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.

2 issues found across 6 files (changes from recent commits).

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="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionComposer.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionComposer.tsx:91">
P3: A timed-out test now shows guidance to an unavailable “Advanced” control, leaving users without the suggested recovery action. Updating the timeout copy to describe the current connection-based flow would keep the error actionable.</violation>
</file>

<file name="apps/api/src/browserbase/browser-evidence-evaluation.ts">

<violation number="1" location="apps/api/src/browserbase/browser-evidence-evaluation.ts:54">
P2: Automation instructions are user-controlled, but this interpolation presents them as part of the evaluator's instructions with no untrusted-data boundary. A crafted instruction can override the target/verdict guidance and produce a false pass; delimiting the value and explicitly telling the evaluator not to follow embedded instructions would keep it as data.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

'Only return pass=true if the evidence is unambiguously present and visible.',
'If it is ambiguous, missing, or contradicted, return pass=false.',
target
? `The automation was asked to: "${target}". Judge the criteria about THAT specific target — ignore unrelated items that merely happen to appear on the page (e.g. a matching value belonging to a different item does not count).`

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: Automation instructions are user-controlled, but this interpolation presents them as part of the evaluator's instructions with no untrusted-data boundary. A crafted instruction can override the target/verdict guidance and produce a false pass; delimiting the value and explicitly telling the evaluator not to follow embedded instructions would keep it as data.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-evidence-evaluation.ts, line 54:

<comment>Automation instructions are user-controlled, but this interpolation presents them as part of the evaluator's instructions with no untrusted-data boundary. A crafted instruction can override the target/verdict guidance and produce a false pass; delimiting the value and explicitly telling the evaluator not to follow embedded instructions would keep it as data.</comment>

<file context>
@@ -43,16 +46,22 @@ export async function evaluateIfNeeded({
-        'Only return pass=true if the evidence is unambiguously present and visible.',
-        'If it is ambiguous, missing, or contradicted, return pass=false.',
+        target
+          ? `The automation was asked to: "${target}". Judge the criteria about THAT specific target — ignore unrelated items that merely happen to appear on the page (e.g. a matching value belonging to a different item does not count).`
+          : '',
+        'Decide whether the page clearly satisfies this criteria for the intended target.',
</file context>
Fix with cubic


const { startTest, closeTestSession, isStarting } = useInstructionTest();

// The AI starts from the connection and finds its own way — no manual start URL.

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: A timed-out test now shows guidance to an unavailable “Advanced” control, leaving users without the suggested recovery action. Updating the timeout copy to describe the current connection-based flow would keep the error actionable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/InstructionComposer.tsx, line 91:

<comment>A timed-out test now shows guidance to an unavailable “Advanced” control, leaving users without the suggested recovery action. Updating the timeout copy to describe the current connection-based flow would keep the error actionable.</comment>

<file context>
@@ -92,7 +88,8 @@ export function InstructionComposer({
   const { startTest, closeTestSession, isStarting } = useInstructionTest();
 
-  const targetUrl = advancedOpen && startUrl.trim() ? startUrl.trim() : connection.url;
+  // The AI starts from the connection and finds its own way — no manual start URL.
+  const targetUrl = initialValues?.targetUrl?.trim() || connection.url;
   const host = useMemo(() => hostnameOf(targetUrl), [targetUrl]);
</file context>
Fix with cubic

When automated sign-in succeeds, capture the page the app lands on and use it as
the connection's URL. Runs then open the authenticated app directly instead of a
login page, so the persisted session is reused and credentials aren't re-entered
every time. Genuine expiry still redirects to login and re-authenticates.

@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.

1 issue found across 3 files (changes from recent commits).

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="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectVendorLoginFlow.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectVendorLoginFlow.tsx:145">
P2: Retrying or reusing this mounted flow can bind the new connection to the previous run's URL when the current completion omits `homeUrl`, because this conditional assignment preserves the old `connectedUrl`. Clearing the state for every successful completion (and when starting a new target) would make the fallback use the current `url`.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

if (output?.isLoggedIn) {
// Keep the session open for a brief success beat; the effect below
// closes it and advances to the connected screen.
if (output.homeUrl) setConnectedUrl(output.homeUrl);

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: Retrying or reusing this mounted flow can bind the new connection to the previous run's URL when the current completion omits homeUrl, because this conditional assignment preserves the old connectedUrl. Clearing the state for every successful completion (and when starting a new target) would make the fallback use the current url.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectVendorLoginFlow.tsx, line 145:

<comment>Retrying or reusing this mounted flow can bind the new connection to the previous run's URL when the current completion omits `homeUrl`, because this conditional assignment preserves the old `connectedUrl`. Clearing the state for every successful completion (and when starting a new target) would make the fallback use the current `url`.</comment>

<file context>
@@ -133,12 +136,13 @@ export function ConnectVendorLoginFlow({
       if (output?.isLoggedIn) {
         // Keep the session open for a brief success beat; the effect below
         // closes it and advances to the connected screen.
+        if (output.homeUrl) setConnectedUrl(output.homeUrl);
         setSigninRun(null);
         setStep('signed-in');
</file context>
Suggested change
if (output.homeUrl) setConnectedUrl(output.homeUrl);
setConnectedUrl(output.homeUrl ?? null);
Fix with cubic

Screenshots were viewport-height, so a short page left a tall band of empty
viewport below the content — a wide gap above the audit overlay. Capture the
full page instead: the overlay sits right under the real content and the
evidence is complete.

@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.

1 issue found across 1 file (changes from recent commits).

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="apps/api/src/browserbase/browser-evidence-execution.ts">

<violation number="1" location="apps/api/src/browserbase/browser-evidence-execution.ts:230">
P1: Long authenticated pages now produce an unbounded 1920px-wide artifact, which can make the requested item unreadable, upload unrelated below-fold data, or exhaust browser/API image-processing memory and fail the screenshot stage. A bounded target/viewport capture or a maximum-height fallback would preserve evidence completeness without making every page size unbounded.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

type: 'jpeg',
quality: 80,
fullPage: false,
fullPage: true,

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

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: Long authenticated pages now produce an unbounded 1920px-wide artifact, which can make the requested item unreadable, upload unrelated below-fold data, or exhaust browser/API image-processing memory and fail the screenshot stage. A bounded target/viewport capture or a maximum-height fallback would preserve evidence completeness without making every page size unbounded.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-evidence-execution.ts, line 230:

<comment>Long authenticated pages now produce an unbounded 1920px-wide artifact, which can make the requested item unreadable, upload unrelated below-fold data, or exhaust browser/API image-processing memory and fail the screenshot stage. A bounded target/viewport capture or a maximum-height fallback would preserve evidence completeness without making every page size unbounded.</comment>

<file context>
@@ -221,10 +221,13 @@ export async function executeBrowserEvidence({
       type: 'jpeg',
       quality: 80,
-      fullPage: false,
+      fullPage: true,
     });
 
</file context>
Fix with cubic

After a successful sign-in, go straight into writing the first instruction
instead of bouncing back to a "create" button. And make multiple connections
first-class: each connection has its own "Add instruction", and a "Connect
another vendor" button adds more vendors — any number of connections, each with
any number of instructions.

@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.

3 issues found across 4 files (changes from recent commits).

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="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectVendorLoginFlow.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectVendorLoginFlow.tsx:196">
P2: A slow profile or automation fetch can cause `onConnected` to run more than once: the first callback rerenders the parent, this dependency change resets the 1.3-second timer, and the child remains mounted until those fetches finish. Keeping the completion callback stable or guarding the hand-off with a ref/one-shot flag would prevent duplicate composer opens and duplicate refreshes.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/BrowserAutomationsList.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/BrowserAutomationsList.tsx:220">
P2: Users with a custom role granting `task:create` but not `integration:create` cannot see the new Add instruction action, while users with `integration:create` but not `task:create` see it and receive an authorization failure when saving. The action should use the same `task:create` RBAC permission enforced by the create-automation API.

(Based on your team's feedback about RBAC permission checks.) [c6400176-c1e4-432c-8f20-061f37d5e4a3]</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx:91">
P2: Canceling the signed-in flow while the profile/automation refresh is pending does not cancel the handoff: this continuation can reopen the composer after `handleCancelConnect` has closed it. A cancellation/request-generation guard around the awaited refresh would prevent stale completion from changing the UI.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

onConnected(connectedUrl ?? url);
}, 1300);
return () => clearTimeout(timer);
}, [step, endSession, onConnected, connectedUrl, url]);

@cubic-dev-ai cubic-dev-ai Bot Jul 17, 2026

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: A slow profile or automation fetch can cause onConnected to run more than once: the first callback rerenders the parent, this dependency change resets the 1.3-second timer, and the child remains mounted until those fetches finish. Keeping the completion callback stable or guarding the hand-off with a ref/one-shot flag would prevent duplicate composer opens and duplicate refreshes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectVendorLoginFlow.tsx, line 196:

<comment>A slow profile or automation fetch can cause `onConnected` to run more than once: the first callback rerenders the parent, this dependency change resets the 1.3-second timer, and the child remains mounted until those fetches finish. Keeping the completion callback stable or guarding the hand-off with a ref/one-shot flag would prevent duplicate composer opens and duplicate refreshes.</comment>

<file context>
@@ -184,15 +184,16 @@ export function ConnectVendorLoginFlow({
     }, 1300);
     return () => clearTimeout(timer);
-  }, [step, endSession]);
+  }, [step, endSession, onConnected, connectedUrl, url]);
 
   const handleAnalyze = useCallback(async () => {
</file context>
Fix with cubic

<span className="text-[11px] font-bold uppercase tracking-[0.08em] text-muted-foreground">
Instructions
</span>
{onAddInstruction && canCreateIntegration && connectionRef && (

@cubic-dev-ai cubic-dev-ai Bot Jul 17, 2026

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: Users with a custom role granting task:create but not integration:create cannot see the new Add instruction action, while users with integration:create but not task:create see it and receive an authorization failure when saving. The action should use the same task:create RBAC permission enforced by the create-automation API.

(Based on your team's feedback about RBAC permission checks.) [c6400176-c1e4-432c-8f20-061f37d5e4a3]

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/BrowserAutomationsList.tsx, line 220:

<comment>Users with a custom role granting `task:create` but not `integration:create` cannot see the new Add instruction action, while users with `integration:create` but not `task:create` see it and receive an authorization failure when saving. The action should use the same `task:create` RBAC permission enforced by the create-automation API.

(Based on your team's feedback about RBAC permission checks.) [c6400176-c1e4-432c-8f20-061f37d5e4a3]</comment>

<file context>
@@ -199,8 +213,19 @@ export function BrowserAutomationsList({
+                  <span className="text-[11px] font-bold uppercase tracking-[0.08em] text-muted-foreground">
+                    Instructions
+                  </span>
+                  {onAddInstruction && canCreateIntegration && connectionRef && (
+                    <button
+                      onClick={() => onAddInstruction(connectionRef)}
</file context>
Fix with cubic

// Load the fresh connection before opening the composer so it resolves the
// just-connected vendor (not a stale profile). Keep the connect view up
// during the load so there's no flash back to the empty state.
await Promise.all([fetchProfiles(), automations.fetchAutomations()]);

@cubic-dev-ai cubic-dev-ai Bot Jul 17, 2026

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: Canceling the signed-in flow while the profile/automation refresh is pending does not cancel the handoff: this continuation can reopen the composer after handleCancelConnect has closed it. A cancellation/request-generation guard around the awaited refresh would prevent stale completion from changing the UI.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx, line 91:

<comment>Canceling the signed-in flow while the profile/automation refresh is pending does not cancel the handoff: this continuation can reopen the composer after `handleCancelConnect` has closed it. A cancellation/request-generation guard around the awaited refresh would prevent stale completion from changing the UI.</comment>

<file context>
@@ -76,20 +78,30 @@ export function BrowserAutomations({ taskId, isManualTask = false }: BrowserAuto
+      // Load the fresh connection before opening the composer so it resolves the
+      // just-connected vendor (not a stale profile). Keep the connect view up
+      // during the load so there's no flash back to the empty state.
+      await Promise.all([fetchProfiles(), automations.fetchAutomations()]);
+      setConnectOpen(false);
+      // One flow: go straight into writing the first instruction.
</file context>
Fix with cubic

Passkeys are device-bound and can't complete in Comp's cloud browser, so
offering one was a dead end. Remove passkey from the method chooser. If a site
only supports passkeys, show a plain "can't be connected here" message instead
of a sign-in that can never finish.
Picking SSO no longer just dumps you in a blank browser. The AI opens the vendor,
clicks through to your identity provider, and streams the steps — then hands the
live browser over for you to finish the IdP login (which we can't hold or
automate). If the saved session is still valid it signs in with no interaction
at all. Reuses the live sign-in run via a mode flag.
Reconnect no longer dumps you into a fully manual browser. A password connection
re-signs-in automatically with the stored credentials (no interaction); an SSO
connection has the AI drive to the identity provider so you only finish there.
Reuses the sign-in run and just refreshes the list when done.

@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.

6 issues found across 10 files (changes from recent commits).

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="apps/api/src/browserbase/browser-credential-signin.service.ts">

<violation number="1" location="apps/api/src/browserbase/browser-credential-signin.service.ts:20">
P3: The new `sso_handoff` outcome is not represented by the client-side `AutoSignInFailure` type, so consumers of that exported type cannot model this API result. Keeping the unions aligned or deriving the client type from a shared contract would avoid silently losing the SSO-specific outcome in future handling.</violation>

<violation number="2" location="apps/api/src/browserbase/browser-credential-signin.service.ts:143">
P3: The SSO handoff behavior is currently unprotected by a regression test, so changes to the sign-in timeline, mode dispatch, or cleanup can break the new connect/reconnect flow without detection. A focused test could assert that SSO never resolves the credential vault, returns `sso_handoff`, and still releases the Stagehand handle while leaving the session open.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useAutoSignin.ts">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useAutoSignin.ts:65">
P2: An SSO request can persist supplied credentials because storage is gated only by the credentials object, not the selected mode. Restrict credential storage to password mode so an SSO flow cannot accidentally retain identity-provider secrets.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx:70">
P2: Manual-password connections are classified as SSO whenever no vault item exists, so reconnecting a profile created while 1Password is unavailable (or after any manual password fallback) takes the wrong sign-in path and can fail even though the user can sign in manually. Persist the authentication method separately, or use a manual/password fallback when no vault item exists and select `sso` only for an explicitly SSO-marked profile.</violation>

<violation number="2" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx:126">
P3: Reconnect completion can be delayed or repeatedly reset by unrelated parent renders because this callback depends on the unstable `context` and `automations` objects, and the child uses `onReconnected` as a timeout-effect dependency. Depend on `context.checkContextStatus` and `automations.fetchAutomations` instead so the callback identity remains stable.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectVendorLoginFlow.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectVendorLoginFlow.tsx:219">
P2: Canceling or navigating away while reconnect is starting can leave the newly created Browserbase session open. The session is only registered in component state after `startSignin` resolves, so `endSession()` cannot clean it up; cancellation handling could abort the request or close the returned `sessionId` when the flow is no longer mounted.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic


// Password mode stores the login for unattended replay; SSO has nothing
// to store — the user finishes at their identity provider.
if (credentials) {

@cubic-dev-ai cubic-dev-ai Bot Jul 17, 2026

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: An SSO request can persist supplied credentials because storage is gated only by the credentials object, not the selected mode. Restrict credential storage to password mode so an SSO flow cannot accidentally retain identity-provider secrets.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useAutoSignin.ts, line 65:

<comment>An SSO request can persist supplied credentials because storage is gated only by the credentials object, not the selected mode. Restrict credential storage to password mode so an SSO flow cannot accidentally retain identity-provider secrets.</comment>

<file context>
@@ -57,37 +60,41 @@ export function useAutoSignin() {
-            toast.error(credRes.error || 'Could not store the login.');
+        // Password mode stores the login for unattended replay; SSO has nothing
+        // to store — the user finishes at their identity provider.
+        if (credentials) {
+          const credRes = await apiClient.post(
+            `/v1/browserbase/profiles/${profileId}/credentials`,
</file context>
Suggested change
if (credentials) {
if (mode === 'password' && credentials) {
Fix with cubic

// AI drives to the identity provider and the user finishes there.
const mode: 'password' | 'sso' = profile?.vaultExternalItemRef
? 'password'
: 'sso';

@cubic-dev-ai cubic-dev-ai Bot Jul 17, 2026

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: Manual-password connections are classified as SSO whenever no vault item exists, so reconnecting a profile created while 1Password is unavailable (or after any manual password fallback) takes the wrong sign-in path and can fail even though the user can sign in manually. Persist the authentication method separately, or use a manual/password fallback when no vault item exists and select sso only for an explicitly SSO-marked profile.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx, line 70:

<comment>Manual-password connections are classified as SSO whenever no vault item exists, so reconnecting a profile created while 1Password is unavailable (or after any manual password fallback) takes the wrong sign-in path and can fail even though the user can sign in manually. Persist the authentication method separately, or use a manual/password fallback when no vault item exists and select `sso` only for an explicitly SSO-marked profile.</comment>

<file context>
@@ -56,10 +62,17 @@ export function BrowserAutomations({ taskId, isManualTask = false }: BrowserAuto
+      // AI drives to the identity provider and the user finishes there.
+      const mode: 'password' | 'sso' = profile?.vaultExternalItemRef
+        ? 'password'
+        : 'sso';
+      clearConnectState(taskId);
+      setReconnectSeed({ url, mode });
</file context>
Fix with cubic

setStep('signing-in');
endSession();
setSigninSteps([]);
void startSignin({ url: reconnect.url, mode: reconnect.mode }).then((handle) => {

@cubic-dev-ai cubic-dev-ai Bot Jul 17, 2026

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: Canceling or navigating away while reconnect is starting can leave the newly created Browserbase session open. The session is only registered in component state after startSignin resolves, so endSession() cannot clean it up; cancellation handling could abort the request or close the returned sessionId when the flow is no longer mounted.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/browser-automations/ConnectVendorLoginFlow.tsx, line 219:

<comment>Canceling or navigating away while reconnect is starting can leave the newly created Browserbase session open. The session is only registered in component state after `startSignin` resolves, so `endSession()` cannot clean it up; cancellation handling could abort the request or close the returned `sessionId` when the flow is no longer mounted.</comment>

<file context>
@@ -190,10 +200,37 @@ export function ConnectVendorLoginFlow({
+    setStep('signing-in');
+    endSession();
+    setSigninSteps([]);
+    void startSignin({ url: reconnect.url, mode: reconnect.mode }).then((handle) => {
+      if (!handle) {
+        // Fall back to a manual sign-in in the live browser.
</file context>
Fix with cubic

// SSO: we can't hold the customer's identity-provider credentials, so the
// AI only clicks through to the provider, then hands the live browser to
// the user to finish there (their IdP login + MFA).
if (mode === 'sso') {

@cubic-dev-ai cubic-dev-ai Bot Jul 17, 2026

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 SSO handoff behavior is currently unprotected by a regression test, so changes to the sign-in timeline, mode dispatch, or cleanup can break the new connect/reconnect flow without detection. A focused test could assert that SSO never resolves the credential vault, returns sso_handoff, and still releases the Stagehand handle while leaving the session open.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-credential-signin.service.ts, line 143:

<comment>The SSO handoff behavior is currently unprotected by a regression test, so changes to the sign-in timeline, mode dispatch, or cleanup can break the new connect/reconnect flow without detection. A focused test could assert that SSO never resolves the credential vault, returns `sso_handoff`, and still releases the Stagehand handle while leaving the session open.</comment>

<file context>
@@ -131,6 +137,25 @@ export class BrowserCredentialSigninService {
+      // SSO: we can't hold the customer's identity-provider credentials, so the
+      // AI only clicks through to the provider, then hands the live browser to
+      // the user to finish there (their IdP login + MFA).
+      if (mode === 'sso') {
+        step('Opening your single sign-on provider');
+        try {
</file context>
Fix with cubic

| 'needs_2fa'
| 'challenge'
// SSO: the AI opened the identity provider; the user finishes the login there.
| 'sso_handoff'

@cubic-dev-ai cubic-dev-ai Bot Jul 17, 2026

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 sso_handoff outcome is not represented by the client-side AutoSignInFailure type, so consumers of that exported type cannot model this API result. Keeping the unions aligned or deriving the client type from a shared contract would avoid silently losing the SSO-specific outcome in future handling.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/browserbase/browser-credential-signin.service.ts, line 20:

<comment>The new `sso_handoff` outcome is not represented by the client-side `AutoSignInFailure` type, so consumers of that exported type cannot model this API result. Keeping the unions aligned or deriving the client type from a shared contract would avoid silently losing the SSO-specific outcome in future handling.</comment>

<file context>
@@ -16,6 +16,8 @@ export type AutoSignInFailure =
   | 'needs_2fa'
   | 'challenge'
+  // SSO: the AI opened the identity provider; the user finishes the login there.
+  | 'sso_handoff'
   | 'unknown';
 
</file context>
Fix with cubic

context.checkContextStatus();
fetchProfiles();
automations.fetchAutomations();
}, [taskId, context, fetchProfiles, automations]);

@cubic-dev-ai cubic-dev-ai Bot Jul 17, 2026

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: Reconnect completion can be delayed or repeatedly reset by unrelated parent renders because this callback depends on the unstable context and automations objects, and the child uses onReconnected as a timeout-effect dependency. Depend on context.checkContextStatus and automations.fetchAutomations instead so the callback identity remains stable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/BrowserAutomations.tsx, line 126:

<comment>Reconnect completion can be delayed or repeatedly reset by unrelated parent renders because this callback depends on the unstable `context` and `automations` objects, and the child uses `onReconnected` as a timeout-effect dependency. Depend on `context.checkContextStatus` and `automations.fetchAutomations` instead so the callback identity remains stable.</comment>

<file context>
@@ -102,9 +115,20 @@ export function BrowserAutomations({ taskId, isManualTask = false }: BrowserAuto
+    context.checkContextStatus();
+    fetchProfiles();
+    automations.fetchAutomations();
+  }, [taskId, context, fetchProfiles, automations]);
+
   const handleCancelConnect = useCallback(() => {
</file context>
Suggested change
}, [taskId, context, fetchProfiles, automations]);
}, [taskId, context.checkContextStatus, fetchProfiles, automations.fetchAutomations]);
Fix with cubic

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant