Skip to content

ci: add PR validation workflow#723

Open
jaeyunha wants to merge 70 commits into
stagingfrom
ci/add-pr-validation
Open

ci: add PR validation workflow#723
jaeyunha wants to merge 70 commits into
stagingfrom
ci/add-pr-validation

Conversation

@jaeyunha

Copy link
Copy Markdown
Member

Summary

  • Adds .github/workflows/ci.yml — a PR validation workflow that runs on every PR to staging/main and on push to those branches.
  • All checks in the required path were verified green locally against the staging branch before merging.
  • Web TypeScript typecheck is non-blocking due to pre-existing errors (see below).

What runs

Job 1: Go API (build + tests + contract guards) — REQUIRED

Check Command Local result
Go API build go build ./... exit 0
Go unit tests go test ./internal/... exit 0 (all packages pass)
OpenAPI coverage node scripts/check-openapi-coverage.mjs exit 0
OpenAPI strict stubs node scripts/check-go-openapi-generated.mjs exit 0
sqlc generated queries node scripts/check-sqlc-generated.mjs exit 0
Migration filename guard node scripts/check-migrations.mjs exit 0
API Dockerfile shape bash infra/docker/api.Dockerfile.test.sh exit 0
ECS task-definition shape node scripts/check-ecs-task-definitions.mjs exit 0
ECS task-definition rendering node scripts/render-ecs-task-definitions.test.mjs exit 0
Deploy-script syntax bash -n ... && node scripts/check-deploy-scripts.mjs exit 0
Smoke-script shape sh -n ... && node scripts/check-smoke-script.mjs exit 0

Job 2: Node (lint + architecture guards + unit tests) — REQUIRED

Check Command Local result
Biome lint + format pnpm lint exit 0
Web API-route empty pnpm web-api-empty exit 0
Web SDK-usage guard node scripts/check-web-sdk-usage.mjs exit 0
Web runtime-boundary node scripts/check-web-runtime-boundaries.mjs exit 0
Vitest unit tests pnpm test --filter '!@exponential/web' exit 0 (sdk, cli, mcp-server all pass)

Job 3: Web typecheck (non-blocking) — NON-BLOCKING (continue-on-error: true)

pnpm typecheck currently fails with pre-existing TypeScript syntax errors in:

  • apps/web/src/app/(app)/settings/integrations/page.tsx
  • apps/web/src/components/issue-detail-view.tsx
  • apps/web/tests/integrations-view.test.tsx

These are not regressions introduced by this PR. The job is visible in CI so progress is tracked, but it does not block merging. ACTION REQUIRED once fixed: remove continue-on-error: true from the web-typecheck job to make it a required gate.

Checks intentionally excluded

  • Playwright E2E (make test-e2e): requires a running dev server (Postgres + Redis + API + web). Environment-blocked in GitHub-hosted runners without significant service setup. Excluded entirely — should be added as a separate workflow once a suitable runner or service configuration is established.
  • pnpm typecheck for @exponential/web in the Vitest job: same pre-existing TS errors prevent compilation. The non-blocking web-typecheck job covers visibility.

🤖 Generated with Claude Code

jaeyunha and others added 30 commits June 10, 2026 07:26
Promote staging to main after clean combined validation: make check, make test, and make test-e2e (144 passed).
Remove the web-side Drizzle/Better Auth runtime path reintroduced by the staging release and restore the last successfully deployed web architecture. Verified with web build, make check, make test, and infra/docker/web.Dockerfile build.
Restore the release web runtime path and repair Stripe checkout/portal through the Go API with deploy configuration.
Keep apps/web UI-only by removing Better Auth/Drizzle runtime modules and adding a boundary guard.
…g alarms

- prepare-ecs-deploy-env.sh: when ALARM_EMAIL is set, idempotently create
  an SNS topic (${APP_NAME}-alarms) and subscribe the operator email; store
  the resulting ARN as ALARM_TOPIC_ARN in .env and print a confirmation
  reminder about the required subscription email. Falls back gracefully when
  neither ALARM_EMAIL nor ALARM_TOPIC_ARN is provided.
- deploy-ecs.sh: export ALARM_TOPIC_ARN before invoking
  configure-ecs-autoscaling.sh so the variable is available in the
  subprocess even when it was not set in the calling shell.
- check-deploy-scripts.mjs: add guard assertions verifying that the SNS
  wiring (sns create-topic, sns subscribe, ALARM_EMAIL, ALARM_TOPIC_ARN) is
  present in prepare-ecs-deploy-env.sh and that deploy-ecs.sh exports
  ALARM_TOPIC_ARN.

Closes #638

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add quickstart one-liner, comparison table vs Linear/Jira/Plane,
workflow GIF placeholder with instructions, MCP and CLI sections,
ELv2 license statement, and set GitHub repo topics via API.

Closes #643

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…irect in ECS preflight

Extends scripts/preflight.sh to consume ACM_CERT_ARN: when set, creates an
HTTPS:443 ALB listener with TLS 1.3 policy wired to the existing target groups,
converts HTTP:80 to a permanent 301 redirect, and routes /api/* on the HTTPS
listener to the Go API. Runs are idempotent — existing listeners are detected
and updated. Documents the full ACM certificate request, DNS validation (manual
and Route 53-automated), and app URL configuration steps in docs/self-hosting.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a GitHub Actions workflow that builds and pushes multi-arch
(linux/amd64 + linux/arm64) exponential-api and exponential-web images
to GHCR on every release, tagged with the release version and `latest`.
Introduces docker-compose.images.yml as the recommended self-hosting
path (no build step, <5 min to running instance), and updates
docs/self-hosting.md to present the image-based quick start first.
Adds IMAGE_TAG to .env.example for version pinning.

Closes #634

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Self-hosted community deployments are blocked mid-script by Stripe secrets
they don't have and don't need. This change makes STRIPE_WEBHOOK_SIGNING_SECRET
and STRIPE_SECRET_KEY (plus the price-ID vars) optional throughout the ECS
deploy path:

- prepare-ecs-deploy-env.sh: guard Stripe secret upserts behind presence
  checks so the script succeeds without any Stripe configuration
- render-ecs-task-definitions.mjs: treat Stripe ARN and price-ID vars as
  optional; prune their task-definition entries when absent rather than
  rendering empty ARNs that ECS would reject
- render-ecs-task-definitions.test.mjs: cover both billing-enabled and
  non-billing rendering modes
- docs/self-hosting.md: document that Stripe is billing-only for ECS

Closes #636

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… script

- Add .github/workflows/release.yml: tag-triggered GitHub Release pipeline
  that validates package.json versions match the tag, runs tests, builds
  tarballs, publishes both SDK and CLI to npm (with provenance), generates
  per-release changelog notes, and creates a GitHub Release with tarballs.
- Add scripts/tag-release.sh: guards (main branch, clean tree, version match)
  then creates an annotated tag and pushes it to trigger the release workflow.
- Add expn --version / expn version command that reads the installed version
  from package.json at runtime, no token required.
- Add CHANGELOG.md documenting the v0.1.0 initial release and future release
  cadence, following Keep a Changelog format.
- Update .github/workflows/publish-cli.yml header to note it is the manual/
  emergency path; normal releases use tag-release.sh + release.yml.
- Update .github/workflows/README.md with full release runbook.
- Add 3 new tests for --version flag (with EXPN_VERSION test seam, without
  token requirement, semver format assertion).

Closes #639

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a plain SMTP sender to apps/api/internal/email alongside SES and
Opensend so self-hosters can use any SMTP relay (Mailhog, Mailgun,
Postmark, Gmail app password, etc.) without an AWS account or Opensend
key.

- New smtpSender: STARTTLS (port 587 default) and implicit-TLS (port
  465, SMTP_TLS=true) transports; optional SMTP_USERNAME/SMTP_PASSWORD
  AUTH PLAIN; graceful no-auth path for loopback relays (Mailhog).
- Auto-selection order: smtp (SMTP_HOST set) > opensend
  (OPENSEND_API_KEY) > ses (SENDER_EMAIL) > Disabled.
- EMAIL_PROVIDER=smtp|ses|opensend explicit override still works.
- docker-compose.dev.yml: wire api to Mailhog via SMTP envs so the
  magic-link flow works out-of-the-box in the dev stack.
- .env.example: document SMTP_HOST/PORT/USERNAME/PASSWORD/TLS vars.
- docs/self-hosting.md: document provider selection order, SMTP
  quick-start, and Mailhog dev recipe.
- 11 new Go table tests covering provider selection, Mailhog delivery,
  raw-message building (multipart and HTML-only paths), and edge cases.

Closes #635

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…comment, triage)

Extends packages/mcp-server beyond read-only with four write tools:
- create_issue: POST /issues with title, team_id and optional fields
- update_issue: PATCH /issues/{id} for status/priority/assignee/labels
- add_comment: POST /issues/{id}/comments
- triage_issue: PATCH /teams/{key}/triage/{issueID} with accept/decline action

All write tools are annotated readOnlyHint:false; triage_issue also sets
destructiveHint:true. Input schemas use strict Zod validation. Errors map
API problem JSON to MCP tool errors while redacting auth fields.

Read-only tool annotations and registration are unchanged; the tool
registration loop now reads annotations from each ToolDefinition.

Tests updated: removed read-only-only assertion, added per-annotation check
and happy-path tests for all four write tools (12 tests total, all passing).

docs/mcp.md updated to document write tools, scope, and follow-up work.

Closes #640

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bstrate

Implements the integration runtime substrate (issue #568):
- Migration 0007: adds lifecycle_state, health timestamps, credential_ref,
  disconnected_at, and integration_job table with backoff/retry fields
- Go: lifecycle helpers (BackoffDuration, TransitionJobStatus, IsTerminal),
  HealthInfo struct serialized without any secret/credential fields,
  disconnectProvider that revokes credentials and disables jobs while
  preserving historical links (replaces hard-delete)
- OpenAPI: adds IntegrationHealth schema; Integration gains nullable health field
- Web: settings/integrations page shows lifecycle badge, last event/success/
  failure timestamps, health summary, and reconnect/disconnect CTAs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… and ECS command

- Move security callout above the quickstart one-liner and strengthen it
  to warn before network connections, not only before sharing
- Add inline comment and build-time note (~15 min, ~8 GiB RAM) with
  reference to #634 for the image-based path
- Add bash prefix to RUN_PROD_SMOKE=true scripts/deploy-ecs.sh for
  consistency with the two preceding lines

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…min visibility

- Add webhook_delivery table (migration 0007) tracking delivery attempts,
  status, response codes, retry schedule, and dead-letter state
- New apps/api/internal/webhooks package: HMAC-SHA256 signing, outbound HTTP
  delivery, exponential backoff retry (5 max attempts), dead-letter state,
  and EnqueueEvent/ProcessPending helpers
- Emit webhook events fire-and-forget after issue, comment, and label mutations
  (created/updated/deleted) so they never block the user response path
- Add /webhook-deliveries admin endpoint (list + per-delivery retry) requiring
  manager role
- Validate webhook event types against known list on createWebhook
- Expose supportedWebhookEvents in workspace API payload
- Background delivery processor ticker (10s interval) wired into main.go
- 11 unit tests covering signing, verification, backoff, HTTP delivery (success,
  4xx, 5xx, network error), event type validation, and helpers

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dempotency

- Exit 1 (was WARNING+continue) when ACM cert status is not ISSUED; message now
  explicitly warns that HTTP:80 would also stop working via the 301 redirect.
- Guard create-listener return value against 'None' before passing ARN to
  ensure_listener_rule, preventing a broken partial-state after HTTP is already
  converted to a redirect.
- In HTTP-only mode, delete any orphaned HTTPS:443 ALB listener left from a
  previous HTTPS run and remove stale ALB_HTTPS_LISTENER_ARN from .env via a
  new del_env_file helper, restoring true downgrade idempotency.
- docs/self-hosting.md: replace the append-only 'echo >> .env' with a
  grep+sed in-place edit pattern consistent with set_env_file, preventing
  duplicate ACM_CERT_ARN keys on re-runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…currency, pinned .env ref

- Add scope=api / scope=web to cache-from and cache-to in publish-images.yml
  so parallel build-api and build-web jobs use isolated GHA cache buckets and
  cannot evict each other's layers.
- Add top-level concurrency group (publish-images, cancel-in-progress: false)
  to prevent simultaneous workflow runs from racing the same GHCR tags.
- Update docs/self-hosting.md quick-start curl to fetch .env.example from the
  pinned IMAGE_TAG ref rather than main, so variable sets stay aligned with the
  image being deployed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Scope --version flag check to rawArgs[0] only, preventing `expn issues
  watch --version N` from being intercepted and printing the CLI version
  instead of running the watch command.
- Fix GitHub Actions tag glob patterns from POSIX-regex `+` quantifiers
  (invalid in minimatch) to `*` wildcards so the release workflow
  actually fires on real semver tags like v1.2.3.
- Strengthen tag-release.sh clean-tree guard from `git diff --quiet HEAD`
  (misses untracked files) to `git status --porcelain` so untracked
  secrets or generated files are caught before a release tag is pushed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ool tests

- Replace optionalStringField cast with stringField for required `action` field (finding #1)
- Replace unsafe priority cast with new optionalNullableIssuePriority helper (finding #2)
- Use WRITE_ANNOTATIONS instead of destructiveHint:true for triage_issue (finding #3)
- Add request body assertions to all four write-tool happy-path tests (finding #4)
- Add rejection test for triage_issue missing required action field (finding #5)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…configuration error, and test races

- Use mime/quotedprintable.NewWriter to properly encode text/plain and
  text/html body parts; fixes protocol violation for non-ASCII content
  and lines >998 bytes that would produce corrupt messages.
- Replace smtp.PlainAuth-only auth with smtpAuth() helper that inspects
  the server's AUTH advertisement and falls back to AUTH LOGIN for
  shared-hosting relays (cPanel, some Gmail SMTP configs) that only
  advertise LOGIN; update the doc comment to reflect actual behaviour.
- Return a descriptive error (not Disabled{}) when EMAIL_PROVIDER=smtp
  is explicit but SMTP_HOST or SENDER_EMAIL is missing, so the warning
  path in router.go is actually reached.
- Fix TestSMTPSendAgainstMailhog race: send to delivered channel before
  writing "221 Bye" so the channel is populated before smtp.Client.Quit()
  returns; replace non-blocking default select with a 5s timeout.
- Replace conn.Read(buf) in mock SMTP server with bufio.Scanner for
  per-line reading, preventing TCP-coalesced multi-command reads from
  silently dropping commands on fast machines.
- buildRawMessage now returns ([]byte, error) to propagate QP encoder
  failures up the call stack.
- Add TestSMTPBuildRawMessageNonASCII to catch the QP encoding regression.
- Add TestNewSMTPExplicitMissingHostReturnsError and
  TestNewSMTPExplicitMissingFromReturnsError for the new error path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- CRITICAL: wrap FOR UPDATE SKIP LOCKED and status='delivering' UPDATE in
  an explicit pgx transaction in ProcessPending so row locks are held across
  both statements, preventing duplicate deliveries on concurrent workers
- CRITICAL: add OpenAPI spec entries for GET /workspaces/current/webhook-deliveries
  and POST /workspaces/current/webhook-deliveries/{id}/retry plus WebhookDelivery
  schema so check-openapi-coverage passes
- HIGH: rename migrations to fix 0006 prefix collision — 0006_stripe_webhook_event
  becomes 0007, and 0007_webhook_delivery becomes 0008
- HIGH: add validateWebhookURL with SSRF protection blocking loopback, private,
  link-local, and IMDS (169.254.169.254) addresses; add SSRF test coverage
- MEDIUM: move /webhook-deliveries mount to /workspaces/current/webhook-deliveries
  matching established workspace-management URL pattern
- MEDIUM: extract isManager to auth.IsManager shared helper, removing the
  duplicate definition in webhooks/handler.go and delegating workspaces/handler.go

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The outbound webhooks branch (PR #663) introduced a 0007_stripe_webhook_event.sql
rename from the existing 0006_stripe_webhook_event.sql (which is already applied
on all deployed databases). Even after that branch is corrected to restore 0006 and
use 0007 for webhook_delivery, the integration lifecycle migration needs to be 0008
to sit above the webhook delivery migration and avoid a version-key collision in the
migration runner (which uses filepath.Base as the unique key).

- Rename packages/proto/migrations/0007_integration_lifecycle.sql
  to packages/proto/migrations/0008_integration_lifecycle.sql
- All 13 integrations package tests pass; go build ./... clean

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ta race)

DESTRUCTIVE MIGRATION RENAME (finding #2):
- Restore packages/proto/migrations/0006_stripe_webhook_event.sql to its
  original name. The previous fix commit incorrectly renamed it to
  0007_stripe_webhook_event.sql, which would cause the migration runner
  (filepath.Base as version key) to re-execute it on any DB that already has
  "0006_stripe_webhook_event.sql" applied, creating a ghost version record and
  breaking migration history.
- Rename 0008_webhook_delivery.sql back to 0007_webhook_delivery.sql since
  0006_stripe_webhook_event.sql is now restored to its correct slot.

DATA RACE on skipSSRFValidation (finding #3):
- Remove the package-level `var skipSSRFValidation atomic.Bool` global.
- Add `skipSSRFCheck bool` field to the Deliverer struct, a
  `WithSSRFCheckDisabled()` functional option, and a `NewDeliverer` constructor.
- Change `sendWebhookRequest` to accept an explicit `skipSSRFCheck bool`
  parameter, eliminating all shared mutable state between parallel tests.
- Update deliver_test.go: remove `setupSSRFBypass` helper and pass `true`
  directly to sendWebhookRequest in tests that use httptest 127.0.0.1 servers.
- All 12 webhooks tests pass under `go test -race`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jaeyunha and others added 27 commits June 13, 2026 03:46
feat(mcp): add write tools to local MCP server (create/update issue, comment, triage)
feat: outbound webhook delivery with signing, retry, and admin visibility
…tform

feat: integration platform lifecycle, health, secrets, and job substrate (#568)
… directory

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
chore: remove stray .release tarballs from main
# Conflicts:
#	apps/api/internal/integrations/handler.go
#	apps/api/internal/integrations/handler_test.go
#	apps/web/src/app/(app)/settings/integrations/page.tsx
#	apps/web/src/lib/db/schema.ts
#	apps/web/tests/integrations-view.test.tsx
#	packages/proto/openapi.yaml
Promote validated staging batch (#630 #631 #633 #649 #651 #665 #668 #669 #670 #671) to main.
Promote validated staging batch (#672 #673 #674 #675 #676 #677) to main.
* fix(web): restore TTY terminal login design

The faithful TTY/terminal auth surface shipped in 0b723a6 was silently
overwritten by the staging merge 94e8a7f, which resurrected origin/main's
stale rounded-pill "chooser" auth-page during a path relocation. Follow-up
commits then realigned tests around the regressed chooser, cementing it.

Re-skin auth-page.tsx back to the redesign.html `TtyLogin` mock — two-column
terminal layout, top chrome bar, sharp bordered method tiles, preflight
doctor panel (real data), recent-sessions table, CLI pairing block, and the
vim-style command bar — WITHOUT regressing auth behavior. All current logic
is preserved: Google OAuth, email magic-link, SAML SSO, passkey, Turnstile,
signup wizard, workspace slug-check, recent-sessions/preflight fetches.

The right column no longer collapses to an empty void (preflight + CLI
pairing always render). Uses --auth-* tokens, works in dark and light.

Validation:
- make check, make test
- web vitest: 1069 passed
- e2e (unauth project, live stack): 20 passed
- visual: dark, light, and email step verified

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

* fix(web): make TTY auth surface fill the viewport height

The root used h-full inside a min-h-screen flex-col layout, so it
collapsed to content height — leaving a black void below and the
command bar floating mid-screen. Use flex-1 so it grows to fill.

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

* feat(web): gate login to Google only, mark other methods coming soon

Disable email magic-link, SAML SSO, and passkey buttons on the auth
chooser and add a "coming soon" caption to each; Google OAuth remains
the only active sign-in method. Underlying handlers are unchanged — only
the UI entry points are gated.

Update tests to the new contract: assert Google stays enabled while
email/SAML/passkey are disabled and show "coming soon"; remove e2e flows
that drove the now-disabled email step.

Validation: make check, make test, web vitest (1068), e2e unauth (18).

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The runner precheck added in #670 calls the GitHub self-hosted runners
API, which requires repository Administration access. With no
GH_RUNNER_STATUS_TOKEN configured it falls back to the default
GITHUB_TOKEN and gets 401/403, hard-failing every deploy since Jun 15 —
even though the Mac mini runner is online.

Treat an auth failure (401/403) as "cannot verify" and skip the precheck
with a warning instead of failing the deploy. The gate stays strict when
a capable token is present, and the deploy job still fails safely if no
runner picks it up. Set GH_RUNNER_STATUS_TOKEN to re-enable the gate.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: add github app integration ingress

* fix(integrations): correct GitHub connect redirect and webhook 202 for no-installation events

- Web: read installationUrl (not authorizationUrl) when provider is
  github so Connect/Reconnect actually redirects to the GitHub App
  installation page
- Web: handle ?github=connected / ?github=canceled query params on
  return from GitHub so a success/error notice is shown and the URL
  is cleaned up
- API: return HTTP 202 with ignored=no_installation_context (instead
  of 400) for webhook events that carry no installation field (e.g.
  ping, github_app_authorization), matching the existing
  unknown_installation path so GitHub marks deliveries as succeeded

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>
…in-702

# Conflicts:
#	apps/api/internal/integrations/github.go
#	apps/api/internal/integrations/handler.go
#	apps/api/internal/integrations/handler_test.go
#	apps/web/src/app/(app)/settings/integrations/page.tsx
Promote validated staging integration batch to main.
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: 244598ad89

ℹ️ 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 +155 to +156
where wd.status in ('pending', 'delivering')
and (wd.next_attempt_at is null or wd.next_attempt_at <= now())

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 Prevent concurrent workers from re-sending deliveries

When the API service is scaled to more than one task, one worker can mark a row as delivering and commit before the HTTP POST finishes, while another worker immediately selects the same row because delivering is still eligible and next_attempt_at is unchanged. That makes outbound webhooks duplicate side effects for any delivery that overlaps another worker tick; use only pending here or add a lease/timeout before recovering delivering rows.

Useful? React with 👍 / 👎.

req.Header.Set("X-Hub-Signature-256", sig)
}

resp, err := deliveryClient.Do(req)

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 Block redirects before sending webhook requests

For attacker-controlled webhook URLs, this validates only the original URL, but Go's default http.Client follows redirects automatically. A public endpoint can return a 302 to 127.0.0.1, link-local metadata, or another private address and bypass the SSRF checks above; configure CheckRedirect to revalidate or reject redirects for webhook delivery.

Useful? React with 👍 / 👎.

$ref: '#/components/responses/Problem'
/workspaces/current/webhook-deliveries:
get:
operationId: listWebhookDeliveries

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 Regenerate clients for webhook delivery endpoints

These new OpenAPI operations are not present in the generated SDK or Go strict-server stubs: I checked packages/sdk/src/generated.ts and apps/api/internal/openapi/openapi.gen.go, and neither contains listWebhookDeliveries or retryWebhookDelivery. Consumers generated from this repo cannot call the delivery-log endpoints until the SDK/stubs are regenerated from this contract change.

Useful? React with 👍 / 👎.

Comment thread docker-compose.images.yml
Comment on lines +115 to +118
AWS_REGION: ${AWS_REGION:-us-east-1}
S3_BUCKET: ${S3_BUCKET:-}
INBOUND_EMAIL_WEBHOOK_SECRET: ${INBOUND_EMAIL_WEBHOOK_SECRET:-}
EXPONENTIAL_INBOUND_DOMAIN: ${EXPONENTIAL_INBOUND_DOMAIN:-}

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 Pass email provider env to the API image

In the prebuilt-image compose stack, self-hosted operators who set SENDER_EMAIL/SMTP_* or Opensend variables in .env still get email disabled because those variables are not forwarded to the API container; only the web container receives SENDER_EMAIL later, but apps/api/internal/email.New is what sends magic-link mail. This makes login email fail in docker-compose.images.yml deployments even when the documented email settings are configured.

Useful? React with 👍 / 👎.

@jaeyunha

Copy link
Copy Markdown
Member Author

Controller disposition for current head 244598ad: validation-blocked; do not merge yet.

Current blocker:

  • .github/workflows/ci.yml configures actions/setup-go@v5 with go-version: "1.25.11", but apps/api/go.mod declares go 1.25.1. That exact patch version is likely unavailable in setup-go and will break the required Go CI job before build/test.

Controller evidence:

  • Diff vs origin/staging is one workflow file: .github/workflows/ci.yml.
  • YAML parse OK (go,node,web-typecheck jobs).
  • Focused guards passed: OpenAPI coverage, generated stubs, deploy/smoke script guards, Biome, web API empty, SDK usage, runtime-boundary, and non-web Vitest.
  • Local Go compile could not be rerun here because this controller runtime has no go binary; CI workflow version pin is the merge blocker.

Required before merge: change setup-go to the repo Go version (1.25.1) or a valid compatible selector, then rerun the required Go workflow.

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