Skip to content

Release staging to main#719

Open
jaeyunha wants to merge 42 commits into
mainfrom
release/staging-main-713
Open

Release staging to main#719
jaeyunha wants to merge 42 commits into
mainfrom
release/staging-main-713

Conversation

@jaeyunha

@jaeyunha jaeyunha commented Jun 17, 2026

Copy link
Copy Markdown
Member

Summary

Promotes current staging to main.

Current delta

Validation

Pending rerun after latest staging reconciliation:

  • make check VERBOSE=1
  • make test VERBOSE=1
  • make test-e2e VERBOSE=1

Notes

Release branch includes current staging plus release-only E2E stabilization commits from prior validation. Merge remains blocked until fresh local gates pass and GitHub branch policy allows merge.

jaeyunha and others added 13 commits June 17, 2026 12:45
* feat: ingest Gong customer call findings

* fix(gong): signature verification, TOCTOU race, empty-id guard, speaker fallback, RFC 7807 412

- Add per-integration sharedSecret generated at OAuth state-save time and
  persisted through completeGongInstall into workspace_integration metadata.
  GongIngestCall reads body bytes, verifies HMAC-SHA256 via X-Gong-Signature
  header using verifyGongSignature (crypto/subtle.ConstantTimeCompare) before
  any DB write.

- Fix resolveGongInstall to return a hard error (surfaced as 400) when
  integrationID is empty, eliminating silent fallback to most-recent
  integration.

- Fix TOCTOU race in createOrLinkGongFinding: extracted tryCreateGongFinding
  with SELECT ... FOR UPDATE on the issue table, wrapped in a retry loop
  (max 3 attempts) that retries on pgconn code 23505 unique violation.

- Fix gongExternalSpeaker: change fallback return from true to false so
  internal members missing from the participants list are not mis-classified
  as external/customer speakers.

- Fix GongConnect 412 response: replace raw map[string]string via problem.JSON
  with problem.Write to emit RFC 7807 application/problem+json.

- Regenerate packages/sdk/src/generated.ts from packages/proto/openapi.yaml;
  gong paths now appear in the TypeScript SDK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(gong): move call ingestion webhook to public router

Gong's servers post to POST /gong/{integrationID}/calls with only an
X-Gong-Signature header — no session cookie or bearer token. The route
was previously mounted under the session-authenticated protected group,
so every inbound webhook received 401 before the HMAC check could run.

Move the route to the publicProvider group (same group as SlackEvents
and GitLabWebhook) so unauthenticated Gong requests reach the handler.
Remove the canManage/auth.FromContext guard from GongIngestCall (the
HMAC signature check is the correct auth boundary for this endpoint).
Add resolveGongWebhookInstall to look up the integration by ID alone,
mirroring the resolveGitLabWebhookInstall pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: add zendesk ticket issue linking

* fix(zendesk): address code review findings in ticket linking

- Replace dynamic SQL string concatenation in resolveZendeskInstall with
  two separate static QueryRow calls (one for integrationID path, one for
  subdomain path) to eliminate SQL injection risk from Go string concat
- Restore early-return guard in queueSentryAutomations when links slice
  is empty so the workflow_state category query no longer runs on every
  issue state-change with no Sentry/Zendesk links
- Change ZendeskTicketCreate error response from 400 to 500 for internal
  server failures (DB errors, missing workflow state); 400 is reserved
  for input validation failures
- Assign getZendeskSourceLink(event) to a const in issue-detail-view.tsx
  to avoid calling the function twice in the same render

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: add Intercom conversation issue linking

* fix(intercom): address security and correctness issues from code review

- intercomSigningSecret: drop AUTH_INTERCOM_SECRET fallback; return ""
  when INTERCOM_SIGNING_SECRET is absent so the 503 path fires correctly
  instead of accepting requests signed with the OAuth client secret
- IntercomIssueUnlink: check RowsAffected() and return 404 when no row
  matched, consistent with IntercomIssueLink behaviour
- intercomIssueDescriptionHTML: guard Permalink with HasPrefix("https://")
  to block javascript: URI injection before building the <a href>
- IntercomConnect 412 path: replace problem.JSON map with problem.Write
  for RFC 7807 consistency

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: add Front conversation issue integration

* fix(integrations): harden Front integration security and correctness

- SSRF: replace unvalidated frontAPIBaseURL with normalizeFrontBaseURL
  that requires https and a .frontapp.com host on admin-supplied values
- HTTP client timeout: pass 10s-timeout client to validateFrontToken
  instead of http.DefaultClient (no timeout)
- Dead branch: failFrontJob now sets status "error" (not "degraded") on
  401/403 permission failures so operators know the token needs rotation
- FrontIssueCreate: distinguish validation sentinels (400) from DB/infra
  errors (500) via isFrontValidationError helper
- frontIssueDescriptionHTML: build combined HTML first, call
  sanitizehtml.RichText once instead of twice

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: add salesforce case links

* fix(salesforce): address SSRF, auth bypass, and double-call findings from code review

- Validate fetchSalesforceUserInfo endpoint against salesforceOAuthBaseURL()
  before making the outbound HTTP request to prevent SSRF via a crafted
  token.ID in the OAuth response.
- Remove the Authorization: Bearer fallback in salesforceSignedAction so
  HMAC signature verification is mandatory for every request; no static
  secret bypass is accepted.
- Move HTTP status checks before json.Decode in exchangeSalesforceOAuth
  and fetchSalesforceUserInfo; include response body in error messages so
  non-2xx failures are surfaced accurately.
- Assign getSalesforceSourceLink(event) to a const in the render path of
  issue-detail-view.tsx to avoid calling the function twice per render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: add Jira guided import slice

* fix(jira-import): address security and correctness issues from code review

- [CRITICAL] Fix silent import truncation: getJSON now decodes 2xx
  responses directly via json.NewDecoder (no 4096 limit); limit kept
  only on error branch for safe logging
- [CRITICAL] Fix JQL injection: validate projectKey against
  ^[A-Z][A-Z0-9_]{0,9}$ before use; wrap value in double-quoted JQL
- [HIGH] Require HTTPS in normalizeJiraBaseURL; reject http:// with a
  clear error
- [HIGH] Fix N+1 query: fetch team key once before the issue import loop
  instead of querying per issue via jiraTeamIdentifier
- [HIGH] Add pagination to issues(): startAt-based loop fetches all
  pages; surface jiraTruncatedError warning when a hard cap truncates;
  add Total/StartAt to jiraSearchResponse
- [MEDIUM] openapi.yaml: add writeOnly:true and description to token
  field on WorkspaceImportExportActionRequest
- [LOW] web: change jiraBaseUrl initial state to "" (placeholder only)
- Update handler_test.go to expect quoted JQL and include total in mock

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(jira-import): make maxResults=0 fetch all pages; remove dead helper; add pagination test

- issues(): replace the maxResults<=0→100 reset with an `unlimited` bool.
  When unlimited, only startAt>=response.Total or an empty page terminates
  the loop — fetching 100-issue batches across as many pages as needed.
  When a positive maxResults cap is supplied the hard-cap exit fires as
  before and surfaces a jiraTruncatedError.  The empty-page guard remains
  so a misbehaving server can never cause an infinite loop.
- Remove dead jiraTeamIdentifier() — confirmed unreferenced after the N+1
  fix that inlined the team-key lookup into importJiraIssues().
- Add TestJiraIssuesPaginatesAllPages: mock server returns total=150 across
  two pages (100+50); asserts all 150 issues are returned and exactly 2
  HTTP calls are made when maxResults=0.
- Add TestNormalizeJiraBaseURLRejectsHTTP: asserts http:// base URLs are
  rejected with an HTTPS error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: add Figma issue previews

* fix(figma): code review fixes for PR #686

- Rename migration 0009_figma_sources.sql → 0010_figma_sources.sql to
  avoid collision with 0009_gitlab_merge_request_links.sql on staging
- Change timestamp columns to timestamptz in figma_source table
- Add format: uuid to the id path param on the refresh endpoint in OpenAPI
- Update OpenAPI summary to be truthful: endpoint stamps refreshed_at only,
  does not call the Figma API
- Regenerate SDK from updated OpenAPI spec
- Fix RefreshFigmaSource auth check: use p, ok := auth.FromContext and
  return 401 if not ok (was silently discarding the ok bool)
- Use SDK client (createBrowserApiClient) for handleFigmaRefresh instead
  of hardcoded fetch URL; remove local FigmaSource interface redeclaration
  and use components["schemas"]["FigmaSource"] from the SDK
- Validate thumbnailUrl in FigmaPreviewCard: only render <img> when URL
  starts with https:// to prevent XSS via untrusted URLs
- Update UI button label from "Refresh" to "Mark seen" to match reality

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: enrich triage routing metadata

* fix(triage): address code review correctness issues

- [CRITICAL] applyTriageDecision no longer writes to http.ResponseWriter;
  server errors return (nil, 500) so BulkTriage can abort the batch
  safely before the response is sent, preventing double-write and
  nil-map dereference panics on mid-batch 500-path failures.
  DecideTriage checks status >= 500 and writes a generic title so DB
  internals are never surfaced in the response.

- [HIGH] triageIssues now batch-loads labels (triageIssueLabelsBatch)
  and source context (triageIssueSourceContextBatch) with
  WHERE issue_id = ANY($1::uuid[]) instead of one query per issue,
  eliminating the N+1 on the triage list endpoint.

- [HIGH] optionProjects adds "completed_at is null and canceled_at is
  null" to filter dead projects for both triage routing and issue
  creation — offering them as targets only produced validation errors.

- [HIGH] settings_members.go (~253) now checks errors.As(*triageValidationError)
  before using the message as a 400 title; raw DB errors become 500s
  instead of leaking DB internals into the response. validate* helpers
  return *triageValidationError for user-visible messages.

- [MEDIUM] triageSourceContext: renamed local var "context" to "out"
  to stop shadowing the "context" package import.

- [LOW] validateTriageAssignee: added ::uuid cast on $2 for consistency
  with other UUID parameters in the package.

Deferred (as noted in review): OpenAPI schema tightening MEDIUM,
debounce/E2E LOWs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ntax

- Remove incorrect jira/zendesk fallback config block: both configure
  via their own admin setup forms, not env var checks
- Fix missing closing braces in handler_test.go caused by naive union
  merge of figma and intercom test blocks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Squash merge PR #711 after controller validation.
# Conflicts:
#	apps/api/internal/teams/handler_test.go
#	apps/api/internal/teams/triage.go

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1707d4e3f8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

func (h Handler) validateTriageAssignee(ctx context.Context, team triageTeam, userID string) error {
var ok bool
// $2::uuid cast added for consistency with other UUID parameters.
err := h.DB.QueryRow(ctx, `select exists(select 1 from team_member tm join member m on m.user_id=tm.user_id and m.workspace_id=$3::uuid where tm.team_id=$1::uuid and tm.user_id=$2::uuid)`, team.ID, userID, team.WorkspaceID).Scan(&ok)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Stop casting text user IDs to UUIDs

When a triage default or accept action includes an assignee whose user ID is not a UUID, this validation query fails before it can return a friendly validation result. The schema stores user.id/team_member.user_id as text and existing seeded/test users use values like demo-founder or random base64 strings, so selecting those users as triage assignees will hit invalid input syntax for type uuid instead of accepting the decision/settings.

Useful? React with 👍 / 👎.

Comment on lines +207 to +208
if stateChanged && (category == "completed" || category == "canceled") {
if err := h.queueZendeskAutomations(ctx, tx, workspaceID, after, category); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Queue Zendesk automation outside the Sentry-only path

For an issue that is linked only to Zendesk, moving it to a completed/canceled state will never queue the Zendesk follow-up because this call is inside queueSentryAutomations after the Sentry-link query returns early when len(links) == 0. That means the new Zendesk writeback only runs for issues that also happen to have a Sentry link, which leaves normal Zendesk-only tickets unsynced.

Useful? React with 👍 / 👎.

problem.Write(w, 500, "Update issue failed", err.Error())
return
}
if err := h.queueFrontAutomations(r.Context(), tx, p.WorkspaceID, existing, updated); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid queuing Front writebacks twice

For Front-linked issues that transition to completed/canceled, this queues duplicate Front outbound jobs because queueProviderAutomations already calls queueFrontAutomations before returning. The second explicit call here inserts another identical front/outbound_delivery job in the same transaction, so the worker can reopen/comment on the same conversation twice for one issue update.

Useful? React with 👍 / 👎.

Comment thread packages/proto/openapi.yaml Outdated
required: [provider, name, description, id, status, displayName, externalId, connectedAt, setupRequirement, actions, health, details]
properties:
provider: { type: string, enum: [github, gitlab, jira, slack, sentry, zendesk, discord, microsoft_teams] }
provider: { type: string, enum: [github, gitlab, jira, slack, sentry, zendesk, discord, microsoft_teams, figma] }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include all returned integration providers in the schema

The /integrations response now returns catalog entries for intercom, salesforce, gong, and front, but the Integration.provider enum still excludes those values. Generated clients and OpenAPI response validators will treat valid API responses containing these new providers as out-of-contract, even though the backend now emits them from the catalog.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d7021bdf40

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

func (h Handler) resolveIssueContext(ctx context.Context, p auth.Principal, identifier, teamKey string) (contextSnapshot, error) {
var snapshot contextSnapshot
var description, assignee, projectName, projectSlug sql.NullString
err := h.DB.QueryRow(ctx, `select i.id::text,i.identifier,i.title,coalesce(i.description,''),t.key,ws.name,i.priority::text,coalesce(u.name,u.email,''),p.name,p.slug from issue i join team t on t.id=i.team_id join workflow_state ws on ws.id=i.state_id join member m on m.workspace_id=t.workspace_id and m.user_id=$2 left join "user" u on u.id=i.assignee_id left join project p on p.id=i.project_id where t.workspace_id=$1::uuid and upper(i.identifier)=upper($3) and ($4='' or upper(t.key)=upper($4)) limit 1`, p.WorkspaceID, p.UserID, identifier, teamKey).Scan(&snapshot.ID, &snapshot.Identifier, &snapshot.Title, &description, &snapshot.TeamKey, &snapshot.State, &snapshot.Priority, &assignee, &projectName, &projectSlug)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Enforce private team access for agent contexts

When a non-admin workspace member who is not in a private team enters one of that team's issue identifiers, this query still returns the issue because it only checks workspace membership via member m and never gates t.is_private with team_member or an admin/owner role. The resulting agent snapshot/output includes the private issue title, description, assignee, and project, so creating an agent run leaks private-team issue data to any workspace member who can guess an identifier.

Useful? React with 👍 / 👎.

}
}
var nextNumber int32
if err := tx.QueryRow(ctx, `select coalesce(max(number),0)+1 from issue where team_id=$1::uuid for update`, teamID).Scan(&nextNumber); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid locking an aggregate when allocating Gong issue numbers

For any actionable Gong call that needs a new issue, PostgreSQL rejects FOR UPDATE on this aggregate max(number) query, so tryCreateGongFinding returns an error before inserting the issue and the webhook responds 500 instead of linking the finding. Lock a real row/table or use the existing issue-number allocation pattern without FOR UPDATE on the aggregate result.

Useful? React with 👍 / 👎.

jaeyunha and others added 5 commits June 17, 2026 15:00
Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: port google sheets scheduled sync

* fix: record google sheets oauth failures

* fix: trim google sheets worker whitespace

---------

Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: add guided GitHub import foundation

* fix: use shared bool parser for GitHub imports

---------

Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…in-713

# Conflicts:
#	apps/api/internal/comments/handler.go
#	apps/api/internal/integrations/handler.go
#	apps/api/internal/issues/handler.go
#	apps/api/internal/labels/handler.go
#	apps/web/src/app/(app)/settings/integrations/page.tsx
#	apps/web/src/components/issue-detail-view.tsx
#	apps/web/tests/integrations-view.test.tsx
@jaeyunha

Copy link
Copy Markdown
Member Author

Release notes refreshed for current staging/main heads. Reconciled release branch now includes origin/staging through 7bf39c3 with release head 9c0ca5b; fresh combined validation is next.

@jaeyunha jaeyunha force-pushed the release/staging-main-713 branch from 16e0f9f to 1665cff Compare June 17, 2026 06:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1665cff3ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

updated_at timestamp NOT NULL DEFAULT now()
);

ALTER TABLE customer ADD COLUMN IF NOT EXISTS domain text;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Relax legacy customer source columns in the migration

Because migration 0011 already creates customer/customer_request with source_provider and source_external_id as NOT NULL (plus customer_request.excerpt), these ADD COLUMN compatibility steps leave those legacy constraints in place on both fresh and upgraded databases. The new first-class handlers insert only domain/name/... and title/body/..., so creating a customer or request will fail with NOT NULL violations until this migration drops/defaults/backfills the old Gong-only columns.

Useful? React with 👍 / 👎.

}

func linkedIssuesForRequest(ctx context.Context, db queryer, requestID string) ([]LinkedIssue, error) {
rows, err := db.Query(ctx, `select i.id::text, i.identifier, i.title, t.key from issue_customer_request icr join issue i on i.id=icr.issue_id join team t on t.id=i.team_id where icr.customer_request_id=$1::uuid order by i.updated_at desc`, requestID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Filter private-team issue links from customer responses

When a customer request is linked to an issue in a private team, any non-guest workspace member can load the customer/request response and this query returns the private issue identifier, title, and team key without checking team_member membership or owner/admin role. Since allowCustomerRequests permits all members, customer request responses become a private-issue metadata leak unless linked issues are filtered with the same private-team access predicate used elsewhere.

Useful? React with 👍 / 👎.

jaeyunha and others added 2 commits June 17, 2026 18:18
Brings the released TTY terminal login redesign (#695) and the
insights-overlay opacity fix into staging, plus the rest of current main.

Conflict resolutions:
- comments/issues/labels handlers: keep both realtime sync publish (#699)
  and outbound webhook enqueue (main).
- integrations/handler.go: keep required (non-omitempty) details field and
  the HealthSummary row column; drop duplicate Metadata field.
- teams/triage.go: keep staging's no-writer (map,int) decision contract and
  due_date support; graft in main's issueauthz relationship validation and
  drop the duplicate tx.Begin.
- teams/handler_test.go: keep both added tests.
- settings/integrations page: use the named IntegrationDetails type, extended
  with the Google Sheets fields.

Merge fixups:
- repoint 6 integration disconnect handlers from the removed revokeProvider
  to main's disconnectProvider (arg order swapped).

Migration reconciliation:
- allowlist the parallel-branch 0010/0011 duplicate prefixes in
  check-migrations.mjs (same convention used for 0006-0008).
- add 0012_customer_requests_reconcile.sql to unify the customer /
  customer_request schema introduced independently by the Gong ingestion
  (0010) and the customer-requests CRM (0011): make Gong-only provider/excerpt
  columns nullable so CRM inserts succeed, and guarantee the superset columns
  and upsert indexes regardless of apply order.

make check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…in-713

# Conflicts:
#	apps/api/internal/comments/handler.go
#	apps/api/internal/integrations/handler.go
#	apps/api/internal/issues/handler.go
#	apps/api/internal/labels/handler.go
#	apps/api/internal/teams/triage.go
#	apps/web/src/app/(app)/settings/integrations/page.tsx
@jaeyunha

Copy link
Copy Markdown
Member Author

Release notes refreshed for current staging/main heads. Release branch updated to 8b24df1 after merging origin/staging 83112c5; validation gate is rerunning next.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0039dd8926

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

func (c githubAPIClient) fetchRepoIssues(ctx context.Context, fullName string) ([]githubAPIIssue, error) {
var out []githubAPIIssue
for page := 1; ; page++ {
path := "/repos/" + escapedGitHubRepo(fullName) + "/issues?state=all&per_page=100&page=" + strconv.Itoa(page)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor the open-only GitHub import scope

When the import dialog is left at its default open-only scope, this still requests state=all and then fetches comments for every closed issue before the later confirm step skips them. On repositories with a long closed-issue history, the guided import can be unnecessarily slow or hit GitHub rate limits even though the user did not ask to import closed issues; pass the normalized scope through to the issues request and use state=open unless closed issues are included.

Useful? React with 👍 / 👎.

coalesce(workspace_integration_id::text,'') as integration_id,
created_at
from integration_thread_link
where issue_id=$1::uuid and provider in ('sentry','github','gong','front','salesforce','jira') and external_permalink is not null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don’t emit empty integration IDs for GitHub sources

Including github here makes issue detail responses for GitHub-imported issues return integrationId: "", because the import path creates integration_thread_link rows without workspace_integration_id. That value violates the IssueExternalSource.integrationId UUID contract and can break generated clients/response validators for imported GitHub issues; either omit/nullable this field for unintegrated sources or persist a real integration id.

Useful? React with 👍 / 👎.

Comment on lines +1137 to +1147
const customerRes = await fetch("/api/customers", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: trimmedCustomerName,
domain: customerDomain.trim() || null,
source: "manual",
}),
});
if (!customerRes.ok) {
throw new Error("Create customer failed");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reuse existing customers before creating requests

When adding feedback for a customer that already exists, this always posts to POST /api/customers first. If the same domain is entered the API returns a 409 and the request is never created, while entering the same customer without a domain creates duplicate customer records; the issue-side flow needs to select/find an existing customer or recover from the conflict before posting the new request.

Useful? React with 👍 / 👎.

jaeyunha and others added 2 commits June 17, 2026 19:39
…ity (#726)

- fix hardcoded /home/jaeyunha path -> $HOME/wt (was broken on macOS)
- fix npm install -> pnpm install (this is a pnpm monorepo)
- add remote-branch support (origin/foo: fetch + upstream tracking)
- flatten '/' in branch names for the worktree dir
- resolve repo root via git rev-parse (works from any cwd)
- copy .codex/.agents alongside .claude; clean up + bail on install failure
- add EXPONENTIAL_WORKTREE_OVERRIDE_BASE override

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee93849716

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return gongFindingResult{}, err
}
metadataRaw, _ := json.Marshal(map[string]any{"callId": call.ID, "findingId": finding.ID, "timestampMs": finding.TimestampMs, "speaker": finding.Speaker, "speakerEmail": finding.SpeakerEmail, "accountId": call.Account.ID, "participants": call.Participants})
if _, err := tx.Exec(ctx, `insert into customer_request (workspace_id, customer_id, issue_id, source_provider, source_external_id, source_url, title, excerpt, metadata, created_at, updated_at) values ($1::uuid,$2::uuid,$3::uuid,'gong',$4,$5,$6,$7,$8::jsonb,now(),now()) on conflict (workspace_id, source_provider, source_external_id) do update set issue_id=excluded.issue_id, customer_id=excluded.customer_id, source_url=excluded.source_url, title=excluded.title, excerpt=excluded.excerpt, metadata=excluded.metadata, updated_at=now()`, install.WorkspaceID, customerID, issueID, finding.ID, finding.Permalink, finding.Title, finding.Excerpt, metadataRaw); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Link Gong requests through the new join table

When an actionable Gong finding creates a customer request, this only writes the legacy customer_request.issue_id column. The new issue/customer request surfaces and filters introduced in this change read links exclusively from issue_customer_request, so Gong-created feedback will not appear on the issue detail, customer related-work counts, or customer request linked issues even though it was created for this issue. Return the request id from this upsert and insert the corresponding join row in the same transaction.

Useful? React with 👍 / 👎.

Comment on lines +274 to +280
domain=coalesce($4::text, domain),
revenue=coalesce($5::numeric, revenue),
size=coalesce($6::integer, size),
tier=coalesce($7::text, tier),
status=coalesce($8::text, status),
owner_id=coalesce($9::text, owner_id),
source=coalesce($10::text, source),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor explicit nulls when patching customers

When a client clears a nullable field by sending {"domain": null} (or tier, ownerId, source, etc.), this coalesce treats the explicit null the same as an omitted field and keeps the old value. The OpenAPI PATCH schema exposes these fields as nullable, so users cannot remove an incorrect domain/owner/source once set; track field presence separately and set SQL NULL for present-null values.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a25219fd2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

)}
{isVisible("customers") && (
<SidebarLink
href="/customers"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Route customer links through the workspace slug

This new sidebar item relies on withWorkspaceSlug, but customers is still classified as a public route rather than an app route in the workspace path helper, so the canonical href remains /customers. In a workspace session, clicking Customers opens the public marketing customers page instead of /{workspaceSlug}/customers (and the same helper also leaves direct customer app paths unnormalized), making the new workspace customer area unreachable from this navigation.

Useful? React with 👍 / 👎.

join issue i on i.id=ztl.issue_id
join team t on t.id=i.team_id
join workflow_state ws on ws.id=i.state_id
where ztl.workspace_integration_id=$1::uuid and ztl.ticket_id=$2 and t.workspace_id=$3::uuid and i.archived_at is null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Filter private teams from Zendesk status results

When a ticket is linked before its team is made private, this status query still returns the issue identifier, title, and workflow state to the Zendesk action because it only checks workspace and archive status. The search/link paths in this same handler explicitly require coalesce(t.is_private,false)=false, so this endpoint becomes a private-issue metadata leak to Zendesk users who can view the linked ticket.

Useful? React with 👍 / 👎.

jaeyunha and others added 3 commits June 17, 2026 22:48
Focused controller QA passed: apps/api go test ./..., import/export Vitest 5/5, diff-check clean.
Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
…in-713

# Conflicts:
#	apps/web/tests/import-export-view.test.tsx
@jaeyunha jaeyunha force-pushed the release/staging-main-713 branch from 0fc011d to 9bea0f9 Compare June 17, 2026 14:40

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9bea0f92f4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


func (h Handler) saveJiraIntegration(ctx context.Context, workspaceID string, userID string, credential jiraCredential, account jiraUser, projects []jiraProject) (string, error) {
metadataRaw, _ := json.Marshal(map[string]any{"deployment": credential.Deployment, "baseUrl": credential.BaseURL, "accountId": firstNonEmptyString(account.AccountID, account.Name), "accountName": firstNonEmptyString(account.DisplayName, account.EmailAddress, account.Name), "projectCount": len(projects)})
credentialRaw, _ := json.Marshal(credential)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Encrypt Jira credentials before storing them

When an admin connects Jira, this stores the API token/email as raw JSON in provider_credential.encrypted_payload instead of using encryptedProviderCredentialJSON like the other providers in this change. That leaves Jira secrets readable from the database/backups and also bypasses the legacy backfill/decrypt path used elsewhere, so Jira credentials remain unprotected after setup.

Useful? React with 👍 / 👎.

Comment on lines +410 to +411
update customer_request set title=$3, body=coalesce($4, body), source=coalesce($5, source), source_url=coalesce($6, source_url), external_provider=coalesce($7, external_provider), external_id=coalesce($8, external_id), important=$9, updated_at=now()
where workspace_id=$1::uuid and id=$2::uuid`, p.WorkspaceID, existing.ID, title, cleanPtr(input.Body), cleanPtr(input.Source), cleanPtr(input.SourceURL), cleanPtr(input.ExternalProvider), cleanPtr(input.ExternalID), important)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor explicit nulls when patching requests

For PATCH requests that send nullable fields such as {"body": null} or {"sourceUrl": null}, this coalesce path treats the explicit null the same as an omitted field and keeps the old value. The customer-request PATCH contract exposes these fields as nullable, so users cannot clear an obsolete body/source/external link once it has been set; track field presence separately and write SQL NULL when a field is present-null.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a32ba03a33

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +541 to +542
case "add", "replace":
if err := h.addGroupMembers(r.Context(), workspaceID(r.Context()), role, members); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Replace SCIM group membership on PATCH replace

When a SCIM client reconciles a role group with op: "replace" and a members list, this path only calls addGroupMembers, so users omitted from the replacement payload keep their previous role. For the admin group, removing someone in the IdP can leave them as an Exponential admin until a separate remove/PUT happens; use replaceGroupMembers for replace operations so omitted members are demoted.

Useful? React with 👍 / 👎.

return
}
for _, op := range input.Operations {
members := patchMembers(op.Value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Parse SCIM remove member path filters

For SCIM PATCH remove operations that identify the member in the path, e.g. path: "members[value eq \"<member-id>\"]" with no value, patchMembers(op.Value) returns an empty slice and removeGroupMembers becomes a no-op. IdPs commonly send removals in that shape, so users removed from admin/guest groups can retain their old role; parse the path filter or reject unsupported remove payloads instead of silently succeeding.

Useful? React with 👍 / 👎.

jaeyunha and others added 2 commits June 18, 2026 13:56
Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
…in-713

# Conflicts:
#	apps/web/tests/issue-detail-view.test.tsx

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70ab16954d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
func scimBaseURL(r *http.Request, workspaceID string) string {
return strings.TrimRight(requestOrigin(r), "/") + "/api/scim/" + workspaceID
return strings.TrimRight(requestOrigin(r), "/") + "/scim/v2"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Route SCIM through the API prefix

In the documented ECS/ALB deployment I checked, scripts/preflight.sh routes only /api/* to the Go API and sends everything else to the web target, so this advertised /scim/v2 base URL is handled by the web service rather than the SCIM router. Enabling SCIM then gives IdPs a URL whose /Users and /Groups endpoints are unreachable outside direct API-port access; mount/advertise SCIM under /api/... or add the matching ALB rule.

Useful? React with 👍 / 👎.

func resolveIssueID(ctx context.Context, db queryer, workspaceID, id string) (string, error) {
id = strings.TrimSpace(id)
var issueID string
err := db.QueryRow(ctx, `select i.id::text from issue i join team t on t.id=i.team_id where t.workspace_id=$1::uuid and (i.id::text=$2 or i.identifier=$2) limit 1`, workspaceID, id).Scan(&issueID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Check private-team access before resolving issue links

When a workspace member who is not on a private team supplies that team's issue id or identifier to the customer-request link/create paths, this resolver still returns it because it only checks the workspace. The subsequent insert creates the customer-request association for a private issue, so a member can mutate private-team issue metadata by guessing an identifier; apply the same private-team predicate used elsewhere, allowing public teams, team members, or owner/admin roles.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aab56e1788

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

id := "agent-run-" + short(workspaceID) + "-" + itoa(len(runs)+1)
run := Run{ID: id, Title: input.Title, Prompt: input.Prompt, TeamKey: teamKey, Context: context, Status: "queued", Owner: owner, Target: teamKey + " · " + context, CreatedAt: now, UpdatedAt: now, Output: "Mock agent run queued. The next step is ready for review and can be promoted when a real executor is connected.", Logs: []string{"Created run from Agent dashboard composer.", "Captured context: " + teamKey + " · " + context + "."}, Suggestions: []Suggestion{suggestion(id+"-suggestion-open-issue", "Open linked workspace context", "Review the selected team and target context before handing this task to the real executor.", context, teamKey)}}
func (h Handler) listRuns(ctx context.Context, workspaceID string) ([]Run, error) {
rows, err := h.DB.Query(ctx, `select id::text,title,prompt,team_key,context,status,owner_name,target,created_at,updated_at,output,failure_reason,prompt_config,logs,suggestions from agent_run where workspace_id=$1::uuid order by updated_at desc, created_at desc`, workspaceID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Filter agent-run history by private-team access

When an owner or private-team member creates an agent run for a private issue/team, any other workspace member who can open the agent dashboard receives it because this query only filters by workspace_id. The returned Run includes generated output/logs/suggestions, and workspaceSummary writes issue titles/descriptions and recent private issue titles into output, so private-team content remains visible even if create-time context resolution is tightened.

Useful? React with 👍 / 👎.

jaeyunha and others added 2 commits June 18, 2026 21:17
Co-authored-by: Hermes Controller <hermes-controller@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: df343e9ac3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +513 to +514
where itl.workspace_integration_id=$1::uuid and itl.provider='salesforce' and itl.source_event_id=$2
limit 1`, integrationID, caseID).Scan(&issue.ID, &issue.Identifier, &issue.Title, &issue.TeamKey, &issue.StatusName, &issue.StatusCategory, &issue.Priority)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Filter private teams before returning Salesforce links

When a Salesforce case was linked while its issue's team was public and that team is later made private, SalesforceIssueCreate hits this existing-source lookup before creating anything and returns the issue id, identifier, title/status, and priority without the private-team predicate used by salesforceIssueForLink above. That lets Salesforce users with the signed component on that case view private issue metadata; apply the same coalesce(t.is_private,false)=false/archive filter here or treat it as not found.

Useful? React with 👍 / 👎.

Comment on lines +430 to +431
where itl.workspace_integration_id=$1::uuid and itl.provider='front' and itl.source_event_id=$2
limit 1`, integrationID, frontCreatedSourceEventID(conversationID)).Scan(&issue.ID, &issue.Identifier, &issue.Title, &issue.TeamKey, &issue.StateName, &issue.Assignee)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Hide private Front-created issues on repeat create

When a Front-created issue is later moved under a private team, invoking the create action again for the same conversation returns this existing link before any public-team check, exposing the private issue's identifier, title, status, and assignee to the Front sidebar. frontIssueForLink already filters private and archived issues, so this existing-created lookup should apply the same predicate before returning the issue.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

limit 1000`, workspaceID, after)

P2 Badge Page sync replays until caught up

When a client reconnects after more than 1000 workspace operations were created while it was offline, this limit returns only the first page and the handler then switches to live pub/sub without any continuation or has_more signal. The SDK persists the last replayed version, so operations between that version and the current database max are skipped instead of being replayed; loop/paginate the backlog before entering the live stream.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

_ = wsjsonWrite(r.Context(), conn, replayMessage{Type: "replay", Operations: ops})
recordReplayStatus(principal.WorkspaceID, lastVersion, len(ops))

subscription, _ := SubscribeOperations(r.Context(), principal.WorkspaceID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Subscribe before replaying sync operations

When an operation is committed after loadOperations finishes but before this SubscribeOperations call is active, it is neither included in the replay query nor delivered by Redis pub/sub. In that timing window a connected client misses the operation indefinitely, so issue/comment/project changes can leave other open workspaces stale until a reconnect or manual refresh; subscribe first and then replay from the requested version (or replay again after subscribing).

Useful? React with 👍 / 👎.

ALTER TABLE customer ADD COLUMN IF NOT EXISTS source_external_id text;
ALTER TABLE customer ADD COLUMN IF NOT EXISTS metadata jsonb NOT NULL DEFAULT '{}'::jsonb;

ALTER TABLE customer_request ADD COLUMN IF NOT EXISTS issue_id uuid REFERENCES issue(id) ON DELETE SET NULL;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Backfill legacy customer-request issue links

For databases that already have Gong-created customer_request.issue_id values from the legacy schema, this reconcile migration preserves the legacy column but never inserts matching rows into issue_customer_request. The new customer request reads/counts hydrate links only from issue_customer_request, so upgraded feedback loses its related issue until each row is manually relinked; add an idempotent backfill from non-null customer_request.issue_id.

Useful? React with 👍 / 👎.


func (h Handler) importJiraComments(ctx context.Context, tx pgx.Tx, p auth.Principal, install jiraInstall, members map[string]string, source jiraIssue, issueID string) (int, error) {
count := 0
for _, comment := range source.Fields.Comment.Comments {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fetch paginated Jira comments before importing

For Jira issues with more comments than the page embedded in the search result, this loop imports only source.Fields.Comment.Comments and never follows the issue comment pagination endpoint. Those older/additional comments are silently omitted while the import still reports success, so workspaces with long Jira discussions lose history during backfill/sync.

Useful? React with 👍 / 👎.

i.created_at, i.updated_at, i.archived_at, i.completed_at, i.canceled_at
from issue i
join team t on t.id=i.team_id
where t.workspace_id=$1::uuid and ($2::timestamp is null or i.updated_at>$2::timestamp)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use a lossless Airbyte cursor for tied timestamps

When more than limit records share the same updated_at value, the response sets next_cursor to the last returned timestamp and the next request filters with updated_at > cursor. Any remaining rows at that same timestamp are skipped; bulk imports/updates commonly create timestamp ties, so incremental Airbyte reads can miss records unless the cursor includes a tie-breaker such as (updated_at, id) or the query handles equality safely.

Useful? React with 👍 / 👎.

@jaeyunha jaeyunha mentioned this pull request Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant