Skip to content

fix(self-hosting): pass AI provider keys through to the web container#2036

Open
DPS0340 wants to merge 1 commit into
CapSoftware:mainfrom
DPS0340:fix/self-host-ai-keys
Open

fix(self-hosting): pass AI provider keys through to the web container#2036
DPS0340 wants to merge 1 commit into
CapSoftware:mainfrom
DPS0340:fix/self-host-ai-keys

Conversation

@DPS0340

@DPS0340 DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown

Same wiring-gap class as #2035 / #1993, found by sweeping every var in the env schema against both compose files.

Problem

self-hosting.mdx tells self-hosters to set these:

DEEPGRAM_API_KEY=your-key    # Transcription
GROQ_API_KEY=your-key        # AI summaries (preferred)
OPENAI_API_KEY=your-key      # AI summaries (fallback)

and docker-compose.coolify.env.example lists all three (lines 64, 67, 68). But neither compose file passes any of them into cap-web, so the operator sets them and nothing happens.

The failure is silent, which is what makes it worth fixing rather than just documenting. Both features check the key and return early:

// apps/web/lib/transcribe.ts:32
if (!serverEnv().DEEPGRAM_API_KEY) {
  return { success: false, message: "Missing necessary environment variables" };
}

// apps/web/lib/generate-ai.ts:32
if (!serverEnv().GROQ_API_KEY && !serverEnv().OPENAI_API_KEY) {

So on a self-hosted instance transcription and AI summaries just never produce output, with no configuration error surfaced to the user — they followed the docs and the feature is simply absent.

Fix

Map the three vars in both compose files, matching how the other optional integrations (RESEND_API_KEY, GOOGLE_CLIENT_ID, APPLE_CLIENT_SECRET) are already wired.

Defaulting to empty via :- means behaviour is unchanged for anyone not using these: the vars stay unset, the schema treats them as optional(), and the existing early-returns keep the features off.

How I found it

Extracted every uppercase identifier from packages/env/server.ts and diffed against both compose files. Most of the unmapped ones are legitimately cloud-only (VERCEL_*, STRIPE_*, WORKOS_*, CLOUDFRONT_*, TINYBIRD_*) and shouldn't be in a self-host template. These three are different: the self-hosting docs explicitly instruct users to set them, which makes the omission a bug rather than a scoping decision.

CAP_CHROME_EXTENSION_ID was the fourth in that category and is handled in #2035.

Verification

Both files parse as valid YAML and all three keys resolve:

docker-compose.yml
   DEEPGRAM_API_KEY: '${DEEPGRAM_API_KEY:-}'
   GROQ_API_KEY: '${GROQ_API_KEY:-}'
   OPENAI_API_KEY: '${OPENAI_API_KEY:-}'
docker-compose.coolify.yml
   ...same

No application code touched.

Diff: 2 files, +6/-0. Independent of #2035 — either can merge first.

self-hosting.mdx tells self-hosters to set DEEPGRAM_API_KEY, GROQ_API_KEY
and OPENAI_API_KEY for transcription and AI summaries, and
docker-compose.coolify.env.example lists all three, but neither compose
file passed them into cap-web.

transcribeVideo() returns early on a missing DEEPGRAM_API_KEY and
generateAiMetadata() on missing GROQ/OPENAI keys, so both features
silently no-op on a self-hosted deployment even when the operator
followed the docs.
@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

@tembo please review

@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Same CI note as #2035: Test Self-Hosting and CI are in action_required pending maintainer approval of workflow runs for a first-time contributor.

Worth approving here specifically — test-self-hosting.yml triggers on docker-compose.yml / docker-compose.coolify.yml, which is exactly what this changes, and it stands the full stack up rather than just linting the file.

Locally verified both files parse and all three keys resolve to '${VAR:-}'. The red Vercel is the standard fork deploy-token failure.

@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

@tembo please review

@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

Re-verified this against current main (5b81ee5b) since it has been sitting a while, and the gap is still there. Posting the evidence so this is a read rather than a diff-and-guess.

The three keys are declared in the env schema:

packages/env/server.ts:92   DEEPGRAM_API_KEY: z.string().optional().describe("Audio transcription")
packages/env/server.ts:94   OPENAI_API_KEY:   z.string().optional().describe("AI summaries")
packages/env/server.ts:95   GROQ_API_KEY:     z.string().optional().describe("AI summaries")

The web app reads them at runtime:

apps/web/lib/transcribe.ts:32    if (!serverEnv().DEEPGRAM_API_KEY) { ... }
apps/web/lib/groq-client.ts:7    if (!serverEnv().GROQ_API_KEY) { return null; }
apps/web/lib/generate-ai.ts      (OPENAI_API_KEY)

Neither compose file passes them through:

$ rg -n "DEEPGRAM|GROQ|OPENAI" docker-compose.yml docker-compose.coolify.yml
(no matches)

So a self-hoster who sets DEEPGRAM_API_KEY in their .env gets it loaded by compose but never handed to the cap-web service. The symptom is not a crash, which is why it is easy to miss — transcribeVideo returns early:

return { success: false, message: "Missing necessary environment variables" };

and getGroqClient() silently returns null, so AI summaries just never appear. The user has set the key correctly and the product tells them it is missing.

The patch is 6 added lines, 3 per file, matching the ${VAR:-} style of the GOOGLE_*/APPLE_* entries directly above them, so unset keys stay unset rather than becoming the empty string.

No behaviour changes for anyone not self-hosting.

@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

One more thing worth flagging, because it affects how this PR looks rather than what it does.

The Test Self-Hosting and CI workflows on this PR are sitting in action_required, not failing. GitHub holds workflow runs on a contributor with no merged commits in the repo until a maintainer approves them:

$ gh api "/repos/CapSoftware/Cap/actions/runs" --jq '...select(.name=="CI")'
completed/action_required   fix/self-host-ai-keys        DPS0340
completed/failure           next-base-improvements       ItsEeleeya

ItsEeleeya has a merged commit, so their runs execute; I have zero, so mine queue. That means a self-hosting change is showing up without the self-hosting test having run — which is the one check you'd most want to see here. Nothing is red; the checks simply have not started.

So I ran the part of that workflow this patch can actually affect — compose parsing and env propagation — locally, and here is the before/after.

On current main:

$ DEEPGRAM_API_KEY=dg-test-123 GROQ_API_KEY=groq-test-456 docker-compose config | grep -c "DEEPGRAM\|GROQ\|OPENAI"
0

The keys are set in the environment and still never reach the cap-web service.

On this branch:

$ DEEPGRAM_API_KEY=dg-test-123 GROQ_API_KEY=groq-test-456 OPENAI_API_KEY=oai-test-789 docker-compose config
      DEEPGRAM_API_KEY: dg-test-123
      GROQ_API_KEY: groq-test-456
      OPENAI_API_KEY: oai-test-789

Same result for docker-compose.coolify.yml.

And with nothing set, the ${VAR:-} default keeps them harmless rather than erroring or going undefined:

      DEEPGRAM_API_KEY: ""
      GROQ_API_KEY: ""
      OPENAI_API_KEY: ""

which matches how GOOGLE_CLIENT_ID/APPLE_CLIENT_SECRET behave in the same block, so an operator who does not use transcription sees no change at all.

I can't approve the workflow run myself, so this is the closest I can get to the evidence that check would have produced.

@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

@richiemcilroy — I found the thing that was actually holding these up, and it is one click rather than nine reviews. Correcting my earlier note, which framed this as a review-cost problem.

All nine of my open PRs have CI sitting in action_required. They are not failing — they have never started:

#2030 CI:action_required     #2041 CI:action_required
#2032 CI:action_required     #2042 CI:action_required
#2034 CI:action_required     #2043 CI:action_required
      A/V Sync Tests:action_required
#2036 CI:action_required     #2044 CI:action_required
      Test Self-Hosting:action_required
#2038 CI:action_required

GitHub gates workflow runs on contributors with no merged commit in the repo. I have zero here, so every run queues for approval. Compare a contributor who does have one:

completed/action_required   fix/self-host-ai-keys      DPS0340
completed/failure           next-base-improvements     ItsEeleeya

So from your side these look like PRs with no test evidence, which is a fair reason to skip past them. That was not obvious from my end either until I checked the run list.

Approving the workflow runs costs one action and gives all nine real check results — including Test Self-Hosting on #2036, which is the one change where that specific workflow is the relevant gate.

Two clarifications so this is not read as chasing:

No reply needed if you would rather just approve the runs and look at them later.

@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

Update on the queue: I cut my open PRs from nine to six rather than wait for you to tell me nine was too many. Closed #2034, #2038 and #2043 — none for a defect, all for review cost, each with the reasoning on the PR itself.

What is left, and why each one earned its place:

PR size closes reported by
#2036 +6/-0 self-hosting: DEEPGRAM/GROQ/OPENAI keys never reach the web container
#2030 +30/-8 getVideoAnalytics server action has no access check
#2042 +43/-2 #1840 @pulgueta
#2041 +67/-10 #1883 @rlnt
#2044 +117/-18 #1812 @shreyastaware
#2032 +185/-2 #1982 @geo-chen

Four of six close an issue someone actually filed. The two that do not are the two smallest, and both are asymmetries rather than opinions:

  • fix(self-hosting): pass AI provider keys through to the web container #2036packages/env/server.ts:92-95 declares the keys, transcribe.ts:32 and groq-client.ts:7 read them, and neither compose file passes them through. An operator who sets DEEPGRAM_API_KEY correctly is told "Missing necessary environment variables". Verified with docker-compose config: 0 keys reach cap-web on main, all three on the branch.
  • fix(web): gate getVideoAnalytics server action on video access policy #2030/api/analytics/route.ts gates on VideosPolicy.canView, with a comment saying it exists so "view counts of private / password-protected videos are not disclosed to unauthorized callers." getVideoAnalytics returns the same numbers, is "use server", and has no check at all. Same data, two entry points, one gate.

Still worth knowing: all six have CI in action_required — the runs have never started, because GitHub holds workflows for contributors with no merged commit here. One approval gives you real check results on all of them. The red Vercel — Authorization required to deploy is the fork-preview auth and appears on your own merged #2028 too.

If six is still too many, tell me a number and I will get there. I would rather leave you two you want than six you have to triage.

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