Hi, I am getting error during deployment - project...
# ask-ai
r
Hi, I am getting error during deployment - project does not match agent subdomain [] livekit
t
Thanks for reaching out, Saurabh. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I see you’re hitting the deployment error: “project does not match agent subdomain.” I can help you fix this. That error means the subdomain configured for your agent doesn’t exactly match the subdomain used by your LiveKit Cloud project. Please check the following and tell me what you find (or share the relevant values): 1. Verify your LiveKit project URL ◦ In the LiveKit Cloud dashboard, find your project URL (example format:
<wss://my-project.livekit.cloud>
). Note the exact subdomain (in this example:
my-project
). 1. Check
livekit.toml
◦ Open your agent’s
livekit.toml
and confirm the
subdomain
field exactly matches the project subdomain:
Copy code
[project]
     subdomain = "your-project-subdomain"
• Subdomain must match character-for-character (case, hyphens, etc.). 1. Confirm
LIVEKIT_URL
environment variable ◦ Ensure
LIVEKIT_URL
uses the same subdomain as the project URL (for example:
<wss://my-project.livekit.cloud>
). 1. Common mismatches to watch for ◦ Extra prefixes/suffixes (e.g.,
www
, trailing slashes) ◦ Different casing ◦ Typo or hyphen vs underscore ◦ Using the full hostname in
subdomain
instead of only the subdomain (subdomain should be
my-project
, not
my-project.livekit.cloud
) If you’d like, reply with: • The value of
subdomain
from your
livekit.toml
(redact any secrets), and • The
LIVEKIT_URL
you’re using (or the project URL from the dashboard). With those I’ll tell you exactly which value to change and the next steps to redeploy. Sources: server-sdk-go | livekit/livekit_composite | DeepWiki
r
how to deploy in CI, if
lk cloud auth
needs browser ?
t
Thanks — I’m responding to your question about deploying in CI when
lk cloud auth
requires a browser. You can avoid the interactive browser login in CI by providing credentials as environment variables. The LiveKit CLI will use these instead of running
lk cloud auth
. What to set in your CI environment: • LIVEKIT_API_KEY — your project API key • LIVEKIT_API_SECRET — your project API secret • LIVEKIT_URL — your project URL (for example:
<wss://my-project.livekit.cloud>
) Example (GitHub Actions):
Copy code
env:
  LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
  LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
  LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }}

steps:
  - name: Deploy LiveKit agent
    run: lk agent create
Notes and tips: • Store API key/secret as CI secrets (never commit them into repo). • Ensure
LIVEKIT_URL
uses the correct subdomain (see earlier error you had — subdomain in
livekit.toml
must match the subdomain in
LIVEKIT_URL
). • With these env vars set, you can run any CLI command (e.g.,
lk agent create
,
lk deploy
) non-interactively. If you’d like, tell me: • Which CI provider you’re using (GitHub Actions, GitLab CI, CircleCI, etc.), and • Which
lk
command you need to run in CI. I’ll provide a ready-to-use CI snippet tailored to your provider and deployment step. Sources: server-sdk-js | livekit/livekit_composite | DeepWiki | Deploy LiveKit Agents with GitHub Actions | LiveKit
r
GitHub Actions
t
Thanks — I’ll help you set up a GitHub Actions workflow to deploy a LiveKit agent non-interactively (no browser auth) and avoid the “project does not match agent subdomain” issue. Below you’ll find: • A recommended GitHub Actions workflow that uses CI secrets for authentication. • Key checks to avoid the subdomain mismatch. • Optional steps for building and pushing a Docker image if you use one. Before you add the workflow, make sure these GitHub repository secrets are configured: • LIVEKIT_API_KEY • LIVEKIT_API_SECRET • LIVEKIT_URL (example:
<wss://my-project.livekit.cloud>
) • (optional) DOCKERHUB_USERNAME, DOCKERHUB_TOKEN (or registry credentials) if you build/push images Important: Confirm the subdomain in your agent’s
livekit.toml
exactly matches the subdomain of
LIVEKIT_URL
. For example, if
LIVEKIT_URL
is
<wss://my-project.livekit.cloud>
,
livekit.toml
must contain:
Copy code
[project]
subdomain = "my-project"
Example GitHub Actions workflow (place in .github/workflows/deploy-livekit-agent.yml):
Copy code
name: Deploy LiveKit Agent

on:
  push:
    branches:
      - main

env:
  # These will be provided from repository secrets
  LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
  LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
  LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }}

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Go (if your agent tooling needs Go)
        uses: actions/setup-go@v4
        with:
          go-version: '1.20'

      - name: Install LiveKit CLI
        run: |
          curl -sSL <https://github.com/livekit/cli/releases/latest/download/lk-linux-amd64> -o /usr/local/bin/lk
          chmod +x /usr/local/bin/lk
          lk version

      - name: Validate LIVEKIT_URL / subdomain quick check
        run: |
          echo "LIVEKIT_URL=$LIVEKIT_URL"
          # Extract subdomain from LIVEKIT_URL (strip scheme and domain)
          SUBDOMAIN=$(echo "$LIVEKIT_URL" | sed -E 's#^wss?://([^./]+).*#\1#')
          echo "Detected subdomain: $SUBDOMAIN"
          # Optional: compare with livekit.toml if present
          if [ -f livekit.toml ]; then
            TOML_SUBDOMAIN=$(grep -E '^subdomain' livekit.toml | sed -E 's/.*= *"([^"]+)".*/\1/')
            echo "livekit.toml subdomain: $TOML_SUBDOMAIN"
            if [ "$SUBDOMAIN" != "$TOML_SUBDOMAIN" ]; then
              echo "ERROR: subdomain in livekit.toml ($TOML_SUBDOMAIN) does not match LIVEKIT_URL subdomain ($SUBDOMAIN)"
              exit 1
            fi
          fi

      # Optional: Build and push Docker image
      - name: Log in to DockerHub
        if: needs.build_image.outputs.build_image == 'true' || always()
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push Docker image
        if: always()
        run: |
          # Example: adjust image name/tag as needed
          IMAGE_NAME="your-org/livekit-agent:latest"
          docker build -t $IMAGE_NAME .
          docker push $IMAGE_NAME

      - name: Deploy LiveKit agent (non-interactive)
        env:
          LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
          LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
          LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }}
        run: |
          # Example: create or update an agent using the CLI
          # Replace flags/values with what your deployment requires
          lk agent create --name my-agent --image your-org/livekit-agent:latest --project-subdomain $(echo $LIVEKIT_URL | sed -E 's#^wss?://([^./]+).*#\1#') || true
          # If you already created and want to deploy/update:
          lk agent deploy my-agent --image your-org/livekit-agent:latest
Notes and recommendations • Use secrets for all credentials and never store API key/secret in code. • The small validation step prevents the “project does not match agent subdomain” error by confirming
livekit.toml
(if present) and
LIVEKIT_URL
subdomain match. •
lk
honors
LIVEKIT_API_KEY
,
LIVEKIT_API_SECRET
, and
LIVEKIT_URL
environment variables, so
lk cloud auth
is unnecessary in CI. • Adjust Docker build/push steps to match your registry and image naming conventions. • Replace `lk agent create`/`lk agent deploy` flags with options your agent requires. If you want, tell me: • Whether you build a Docker image in CI or use a public image, • The exact
lk
command you run locally (create/deploy/update), and • Whether you keep
livekit.toml
in the repo, and I’ll tailor the workflow to your specific commands and image registry.
r
- why Go setup isneeded, can we use lk CLI - why Docker build/push —does lk agent not deploy builds the image itself from the Dockerfile on LiveKit
t
Thanks — I’m answering your two questions about the GitHub Actions workflow. 1) Why was the Go setup step included? • The Go step was optional boilerplate in the example. The LiveKit CLI (
lk
) itself is a single binary and does not require Go to run in CI. Remove the Go setup step if your workflow doesn't build Go code. Keep it only if your agent build process or other steps actually need Go in the runner. 2) Do you need to build/push a Docker image in CI, or does
lk agent
build it from the repo/Dockerfile? • The CLI does not automatically build your image from your repo in your CI runner. If you want LiveKit to run a specific container image, you must provide an image the platform can pull (for example, an image pushed to Docker Hub or another registry). • Building and pushing the image in CI ensures the image is available to LiveKit when you run
lk agent create
or
lk agent deploy
. • If you already host your image publicly (or use a registry image tag you control), you can skip the build/push steps and point
lk
at that image. • If your workflow expects LiveKit to build from your repo, that is not the behavior shown in the example; instead, produce and publish the image from CI, then deploy it. If you like, tell me: • Do you want the runner to build and push the image in CI, or do you already have a registry image to reference? • Which exact
lk
command you run locally (create/deploy/update) and whether
livekit.toml
is in the repo. I’ll give a trimmed GitHub Actions workflow that removes the unnecessary Go step and either omits Docker steps (if you already have an image) or includes a focused build/push sequence.
r
I dont have an image, please give a GitHub Actions workflow
t
Thanks — I’ve prepared a ready-to-use GitHub Actions workflow that: • Builds a Docker image from your repository, • Pushes it to Docker Hub (adjustable to other registries), • Deploys a LiveKit agent non-interactively using the
lk
CLI and CI secrets, • Validates the LiveKit subdomain to avoid the “project does not match agent subdomain” error. Before using this workflow, add these repository secrets in GitHub: • LIVEKIT_API_KEY • LIVEKIT_API_SECRET • LIVEKIT_URL (example: wss://my-project.livekit.cloud) • DOCKERHUB_USERNAME • DOCKERHUB_TOKEN Place this file at .github/workflows/deploy-livekit-agent.yml:
Copy code
name: Build & Deploy LiveKit Agent

on:
  push:
    branches:
      - main

env:
  # populated from repository secrets
  LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
  LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
  LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }}
  DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
  DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install LiveKit CLI
        run: |
          curl -sSL <https://github.com/livekit/cli/releases/latest/download/lk-linux-amd64> -o /usr/local/bin/lk
          chmod +x /usr/local/bin/lk
          lk version

      - name: Validate LIVEKIT_URL subdomain and livekit.toml (if present)
        run: |
          if [ -z "$LIVEKIT_URL" ]; then
            echo "LIVEKIT_URL is not set"; exit 1
          fi
          echo "LIVEKIT_URL=$LIVEKIT_URL"
          SUBDOMAIN=$(echo "$LIVEKIT_URL" | sed -E 's#^wss?://([^./]+).*#\1#')
          echo "Detected subdomain: $SUBDOMAIN"
          if [ -f livekit.toml ]; then
            TOML_SUBDOMAIN=$(grep -E '^subdomain' livekit.toml | sed -E 's/.*= *"([^"]+)".*/\1/' || true)
            if [ -n "$TOML_SUBDOMAIN" ]; then
              echo "livekit.toml subdomain: $TOML_SUBDOMAIN"
              if [ "$SUBDOMAIN" != "$TOML_SUBDOMAIN" ]; then
                echo "ERROR: subdomain in livekit.toml ($TOML_SUBDOMAIN) does not match LIVEKIT_URL subdomain ($SUBDOMAIN)"
                exit 1
              fi
            else
              echo "livekit.toml present but no subdomain key found; continuing"
            fi
          fi

      - name: Set image name and tag
        id: vars
        run: |
          IMAGE_NAME="${{ github.repository_owner }}/livekit-agent"
          IMAGE_TAG="${GITHUB_SHA::7}"
          echo "IMAGE_NAME=$IMAGE_NAME" >> $GITHUB_OUTPUT
          echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_OUTPUT

      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build Docker image
        run: |
          IMAGE="${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.vars.outputs.IMAGE_TAG }}"
          echo "Building $IMAGE"
          docker build -t "$IMAGE" .
          echo "::set-output name=image::$IMAGE"
        id: build

      - name: Push Docker image
        run: |
          IMAGE="${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.vars.outputs.IMAGE_TAG }}"
          echo "Pushing $IMAGE"
          docker push "$IMAGE"

      - name: Deploy LiveKit agent (create or update)
        env:
          LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
          LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
          LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }}
        run: |
          IMAGE="${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.vars.outputs.IMAGE_TAG }}"
          AGENT_NAME="my-agent" # change as needed
          SUBDOMAIN=$(echo "$LIVEKIT_URL" | sed -E 's#^wss?://([^./]+).*#\1#')

          # Try to create the agent; if it already exists, update/deploy the image
          set -o pipefail
          if lk agent list | grep -q "^$AGENT_NAME$"; then
            echo "Agent $AGENT_NAME exists — deploying image"
            lk agent deploy "$AGENT_NAME" --image "$IMAGE"
          else
            echo "Creating agent $AGENT_NAME"
            lk agent create --name "$AGENT_NAME" --image "$IMAGE" --project-subdomain "$SUBDOMAIN"
          fi
Quick notes and how to customize: • Change AGENT_NAME to your desired agent name. • IMAGE naming: This workflow pushes to Docker Hub under
${{ github.repository_owner }}/livekit-agent:sha
. Change
IMAGE_NAME
if you prefer a different registry or path. • If using a different registry (ECR, GCR, GitHub Container Registry), replace the Docker login step with the appropriate login action and adjust image naming. • The workflow ensures
LIVEKIT_API_KEY
,
LIVEKIT_API_SECRET
, and
LIVEKIT_URL
are used by the
lk
CLI so no browser
lk cloud auth
is required. • The subdomain check compares
LIVEKIT_URL
with
livekit.toml
(if present) to prevent the “project does not match agent subdomain” error. If you want, I can: • Convert this to use GitHub Container Registry or AWS ECR instead of Docker Hub, • Adjust the image tag strategy (semantic tags, branch-based tagging), • Or tailor the
lk agent create
flags to match any custom agent configuration you need. Which would you like next?
r
my CI is failing
Copy code
name: Deploy Staging

# Agent runtime secrets (OPENAI_API_KEY, DEEPGRAM_API_KEY, etc.) are managed
# separately via the Update Staging Secrets workflow.
# They persist across deployments and only need updating when keys change.

on:
  workflow_dispatch:
    inputs:
      agent:
        description: "Agent to deploy"
        required: true
        type: choice
        options:
          - inbound
          - persistence_demo

jobs:
  deploy:
    name: Deploy ${{ inputs.agent }} to staging
    runs-on: ubuntu-latest
    env:
      LIVEKIT_URL: ${{ secrets.STAGING_LIVEKIT_URL }}
      LIVEKIT_API_KEY: ${{ secrets.STAGING_LIVEKIT_API_KEY }}
      LIVEKIT_API_SECRET: ${{ secrets.STAGING_LIVEKIT_API_SECRET }}
    steps:
      - uses: actions/checkout@v4

      - name: Install lk CLI
        run: |
          curl -sSL <https://get.livekit.io/cli> | bash
          echo "$HOME/.livekit/bin" >> "$GITHUB_PATH"
      - name: Deploy ${{ inputs.agent }} agent
        env:
          LIVEKIT_URL: ${{ secrets.STAGING_LIVEKIT_URL }}
          LIVEKIT_API_KEY: ${{ secrets.STAGING_LIVEKIT_API_KEY }}
          LIVEKIT_API_SECRET: ${{ secrets.STAGING_LIVEKIT_API_SECRET }}
        run: |
          echo "Deploying ${{ inputs.agent }} agent to staging"
          lk agent deploy \
            --config src/agents/${{ inputs.agent }}/livekit.toml \
            --secrets AGENT_TYPE=${{ inputs.agent }} \
            --silent
      - name: Verify deployment
        env:
          LIVEKIT_URL: ${{ secrets.STAGING_LIVEKIT_URL }}
          LIVEKIT_API_KEY: ${{ secrets.STAGING_LIVEKIT_API_KEY }}
          LIVEKIT_API_SECRET: ${{ secrets.STAGING_LIVEKIT_API_SECRET }}
        run: |
          lk agent status \
            --config src/agents/${{ inputs.agent }}/livekit.toml
t
Thanks — I can help troubleshoot this failing workflow. I’m responding to your CI failure for the “Deploy Staging” GitHub Action and the
lk agent deploy
/
lk agent status
steps. First, I need the failing error text. Could you paste the exact error output from the failing job (the logs for the step that failed)? That will let me pinpoint the cause. Meanwhile, here are the most common reasons this workflow fails and quick checks you can run right now: • Missing/incorrect subdomain in
livekit.toml
◦ Symptom: Error like “project does not match agent subdomain”. ◦ Check: Open
src/agents/<agent>/livekit.toml
and confirm:
Copy code
[project]
    subdomain = "your-subdomain"  # must match the subdomain in LIVEKIT_URL
• Confirm
LIVEKIT_URL
secret (example:
<wss://my-project.livekit.cloud>
) and ensure the subdomain (my-project) exactly matches the
subdomain
value. • Authentication problems (API key/secret or LIVEKIT_URL) ◦ Symptom: Authorization or connection errors from
lk
(403/401, connection refused). ◦ Check: Ensure the three secrets are set correctly in repository/organization secrets and are available to the workflow. You already set them in both job env and step env — that’s fine; just confirm values are correct and not expired/rotated. •
lk
CLI install failure or PATH not set ◦ Symptom: “command not found: lk” or version/install errors. ◦ Check: The workflow uses the install script and then appends $HOME/.livekit/bin to PATH. Confirm install succeeded earlier in the logs and that
lk version
shows a version. If install script changed, switch to downloading the binary directly: ▪︎ Example: curl the binary to /usr/local/bin and chmod +x, then run
lk version
. • Agent image not provided / CLI expecting an image ◦ Symptom:
lk agent deploy
errors complaining about missing image or invalid image reference. ◦ Check: If your agent expects an image,
lk agent deploy
must reference an image previously pushed to a registry (or your
livekit.toml
must specify how to build). If you aren’t building images in this workflow, add build/push steps (I can provide a snippet). If your agents are configured to use a remote image, ensure the image URL is reachable and credentials are provided if private. •
--config
path or file not found ◦ Symptom: Errors indicating
livekit.toml
or config path missing. ◦ Check: Confirm repo checkout path contains
src/agents/<agent>/livekit.toml
in that branch/run. If the file is generated earlier or in another job, ensure job dependencies and artifact passing are correct. • Silent/quiet mode hiding helpful errors ◦ Symptom: Fails with no visible output. ◦ Check: Temporarily remove
--silent
from
lk agent deploy
to see full logs from the command. What I need from you to help further (pick any that apply): • The exact failing step logs (copy/paste the error output from GitHub Actions). • The contents (or at least the
subdomain
field) of
src/agents/<agent>/livekit.toml
for the agent you tried to deploy (redact secrets). • Whether your agent uses a Docker image built in CI or references a public/private registry image. • Confirmation that
lk
install step completed successfully (you can paste the
lk version
log line). If you paste the failing log and the
subdomain
value, I’ll tell you the precise fix and an updated workflow snippet (if needed).
r
Copy code
Deploying persistence_demo agent to staging
2026-02-19T09:47:05.130Z	DEBUG	lk	config/livekit.go:89	loading src/agents/persistence_demo/livekit.toml file
Using url, api-key, api-secret from environment
agent ID or [src/agents/persistence_demo/livekit.toml] required
URL: ***, api-key: ***, api-secret: ************
2026-02-19T09:47:05.155Z	DEBUG	lk	config/livekit.go:89	loading src/agents/persistence_demo/livekit.toml file
Using url, api-key, api-secret from environment
URL: ***, api-key: ***, api-secret: ************
huh: could not open a new TTY: open /dev/tty: no such device or address
t
Thanks — I’m responding to the failing CI step that shows the TTY error and the earlier messages about non-interactive deploys. What’s happening • The
lk agent deploy
command in your job is trying to open a TTY (interactive prompt) because it expects either an agent ID or some interactive input when it can’t determine the target to deploy from the config alone. • In CI there’s no /dev/tty, so the CLI errors: “could not open a new TTY: open /dev/tty: no such device or address.” • The logs also show “agent ID or [src/agents/persistence_demo/livekit.toml] required” — the CLI wasn’t given an explicit agent name/ID and the config didn’t supply everything the CLI needed non-interactively. How to fix it (two recommended approaches) Pick one of these depending on how you want to manage deployments. Option A — Deploy by specifying the agent name and image (non-interactive, recommended) • Provide an explicit agent name (or ID) to
lk agent deploy
and ensure the agent has an image configured (either in
livekit.toml
or passed by flag). This avoids any interactive prompt. Example step change (replace IMAGE with your registry/image):
Copy code
- name: Deploy ${{ inputs.agent }} agent
  env:
    LIVEKIT_URL: ${{ secrets.STAGING_LIVEKIT_URL }}
    LIVEKIT_API_KEY: ${{ secrets.STAGING_LIVEKIT_API_KEY }}
    LIVEKIT_API_SECRET: ${{ secrets.STAGING_LIVEKIT_API_SECRET }}
  run: |
    IMAGE="your-org/${{ inputs.agent }}:latest"   # or image built and pushed earlier
    AGENT_NAME="${{ inputs.agent }}-staging"     # pick the agent name you created
    echo "Deploying $AGENT_NAME with image $IMAGE"
    lk agent deploy "$AGENT_NAME" --image "$IMAGE" --config src/agents/${{ inputs.agent }}/livekit.toml --silent
Why this works •
--image
+ explicit agent name gives the CLI everything it needs; it won’t prompt for interactive choices. Option B — Create-or-update flow (if agent may not exist yet) • Try creating the agent non-interactively first (with
lk agent create --name ... --image ... --project-subdomain ...
) and if it already exists, call
lk agent deploy
. That is what I suggested earlier and prevents interactive selection on first run. Example:
Copy code
run: |
  IMAGE="your-org/${{ inputs.agent }}:${GITHUB_SHA::7}"
  AGENT_NAME="${{ inputs.agent }}-staging"
  SUBDOMAIN=$(echo "$LIVEKIT_URL" | sed -E 's#^wss?://([^./]+).*#\1#')

  if lk agent list | grep -q "^$AGENT_NAME$"; then
    lk agent deploy "$AGENT_NAME" --image "$IMAGE" --config src/agents/${{ inputs.agent }}/livekit.toml --silent
  else
    lk agent create --name "$AGENT_NAME" --image "$IMAGE" --project-subdomain "$SUBDOMAIN" --config src/agents/${{ inputs.agent }}/livekit.toml --silent
  fi
Other immediate checks and tips • Ensure your
livekit.toml
contains a valid
project.subdomain
that matches
LIVEKIT_URL
(we saw this earlier). • If you’re not pushing images in this workflow, add build+push steps (or point
--image
to a public image). • Remove
--silent
temporarily while debugging so you see full CLI output in logs. • Do not try to force a TTY in CI (e.g.,
--tty
) — this won’t work because GitHub Actions has no /dev/tty. If you want, I can: • Produce a full patched workflow that builds the image, pushes it, then runs the create-or-update flow for your repo, • Or edit your existing workflow to add the image build/push and the explicit agent name so it runs non-interactively. Which would you prefer? If you want the updated workflow, tell me the image name format you prefer (Docker Hub, GHCR, etc.) and the AGENT_NAME you want to use.
r
Creating persistence_demo agent on staging 2026-02-19T095731.443Z DEBUG lk config/livekit.go:89 loading src/agents/persistence_demo/livekit.toml file Using url, api-key, api-secret from environment URL: *, api-key: *, api-secret: ************ huh: could not open a new TTY: open /dev/tty: no such device or address
t
Thanks — I see you’re still hitting the TTY error when creating/deploying the persistence_demo agent in GitHub Actions: "huh: could not open a new TTY: open /dev/tty: no such device or address" I’ll explain what’s happening and give a minimal, working non-interactive fix you can drop into your workflow. Why this happens • The
lk
CLI is trying to open an interactive prompt (TTY) — typically because it needs confirmation, authentication, or missing required fields (agent id/image/project subdomain) and falls back to an interactive flow. • CI runners (GitHub Actions) do not provide /dev/tty, so any interactive attempt fails with that error. • To run in CI you must provide all required inputs up front (agent id/name, image or build steps, project subdomain) and avoid interactive flags. Quick checklist to make deploy non-interactive • Ensure LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET are set as secrets and exported in the step (you already do this). • Ensure src/agents/persistence_demo/livekit.toml exists and contains the correct project subdomain:
Copy code
[project]
  subdomain = "your-staging-subdomain"
• Provide an explicit agent name/ID and an image (or pre-build the image in CI). Do not rely on the CLI to prompt you for these. • Avoid interactive mode. Use explicit flags (or the deploy-action) so the CLI never tries to open a TTY. Two simple, reliable fixes (pick one) Option 1 — Pre-build image, then deploy with explicit agent name (recommended) • Build & push the Docker image in the workflow. • Call
lk agent create
(first-run) or
lk agent deploy
with explicit agent name and
--image
. This prevents any interactive prompt. Example step to use inside your job (replace image name and AGENT_NAME):
Copy code
- name: Build and push image
  run: |
    IMAGE="your-registry/persistence_demo:${GITHUB_SHA::7}"
    docker build -t "$IMAGE" src/agents/persistence_demo
    echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
    docker push "$IMAGE"
  env:
    DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
    DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

- name: Create or deploy agent (non-interactive)
  env:
    LIVEKIT_URL: ${{ secrets.STAGING_LIVEKIT_URL }}
    LIVEKIT_API_KEY: ${{ secrets.STAGING_LIVEKIT_API_KEY }}
    LIVEKIT_API_SECRET: ${{ secrets.STAGING_LIVEKIT_API_SECRET }}
  run: |
    IMAGE="your-registry/persistence_demo:${GITHUB_SHA::7}"
    AGENT_NAME="persistence_demo-staging"
    SUBDOMAIN=$(echo "$LIVEKIT_URL" | sed -E 's#^wss?://([^./]+).*#\1#')
    if lk agent list | grep -q "^$AGENT_NAME$"; then
      lk agent deploy "$AGENT_NAME" --image "$IMAGE" --config src/agents/persistence_demo/livekit.toml
    else
      lk agent create --name "$AGENT_NAME" --image "$IMAGE" --project-subdomain "$SUBDOMAIN" --config src/agents/persistence_demo/livekit.toml
    fi
Option 2 — Use LiveKit GitHub Action (no TTY, higher-level convenience) • Use the official deploy-action which is CI-friendly and avoids TTY prompts when configured with env secrets and working directory. Example step:
Copy code
- name: Deploy LiveKit Cloud Agent
  uses: livekit/deploy-action@v2
  env:
    LIVEKIT_URL: ${{ secrets.STAGING_LIVEKIT_URL }}
    LIVEKIT_API_KEY: ${{ secrets.STAGING_LIVEKIT_API_KEY }}
    LIVEKIT_API_SECRET: ${{ secrets.STAGING_LIVEKIT_API_SECRET }}
  with:
    OPERATION: deploy
    WORKING_DIRECTORY: src/agents/persistence_demo
    IMAGE: your-registry/persistence_demo:latest   # optional if you want to use pre-built image
Additional debugging tips • Remove
--silent
temporarily so logs show full CLI output while you iterate. • If you still see the TTY error, check the command that runs immediately before it — it’s trying to prompt somewhere (missing
--image
, missing
--name
, or falling back to login flow). • Confirm
livekit.toml
subdomain matches the subdomain parsed from
LIVEKIT_URL
exactly (no case, hyphen, or suffix mismatches). If you’d like, I can: • Provide a full GitHub Actions workflow file (build → push → deploy) tailored to Docker Hub, GHCR, or ECR. • Edit your existing workflow in-place to add image build/push and the create-or-deploy steps so the TTY prompt disappears. Tell me: • Which container registry you prefer (Docker Hub, GHCR, ECR), and • The agent name you want to use (or I can keep using persistence_demo-staging). I’ll return a complete, ready-to-use workflow you can drop into .github/workflows/. Sources: Voice AI quickstart | LiveKit Documentation | Examples | livekit/livekit_composite | DeepWiki | Deploy LiveKit Agents with GitHub Actions | LiveKit | server-sdk-go | livekit/livekit_composite | DeepWiki | Server-side | livekit/livekit_composite | DeepWiki
r
• Edit your existing workflow in-place to add image build/push and the create-or-deploy steps so the TTY prompt disappears.
t
Thanks — I updated your existing workflow in-place to: • Build and push a Docker image for the selected agent, • Create the agent non-interactively if it doesn’t exist, or deploy an updated image if it does, • Avoid any interactive prompts (no TTY), so GitHub Actions won’t hit the "/dev/tty" error. Drop this into your repo as .github/workflows/deploy-staging.yml (it keeps your original structure and inputs). Replace the Docker registry variables and image naming if you use a registry other than Docker Hub.
Copy code
name: Deploy Staging

on:
  workflow_dispatch:
    inputs:
      agent:
        description: "Agent to deploy"
        required: true
        type: choice
        options:
          - inbound
          - persistence_demo

jobs:
  deploy:
    name: Deploy ${{ inputs.agent }} to staging
    runs-on: ubuntu-latest
    env:
      LIVEKIT_URL: ${{ secrets.STAGING_LIVEKIT_URL }}
      LIVEKIT_API_KEY: ${{ secrets.STAGING_LIVEKIT_API_KEY }}
      LIVEKIT_API_SECRET: ${{ secrets.STAGING_LIVEKIT_API_SECRET }}
      # Docker Hub credentials (set these as repo secrets)
      DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
      DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
    steps:
      - uses: actions/checkout@v4

      - name: Install lk CLI
        run: |
          curl -sSL <https://get.livekit.io/cli> | bash
          echo "$HOME/.livekit/bin" >> "$GITHUB_PATH"

      - name: Set image name & tag
        id: image-vars
        run: |
          AGENT="${{ inputs.agent }}"
          IMAGE_NAME="${{ github.repository_owner }}/livekit-${AGENT}"
          IMAGE_TAG="${GITHUB_SHA::7}"
          IMAGE="${IMAGE_NAME}:${IMAGE_TAG}"
          echo "AGENT=${AGENT}" >> $GITHUB_OUTPUT
          echo "IMAGE=${IMAGE}" >> $GITHUB_OUTPUT
          echo "IMAGE_NAME=${IMAGE_NAME}" >> $GITHUB_OUTPUT
          echo "IMAGE_TAG=${IMAGE_TAG}" >> $GITHUB_OUTPUT

      - name: Log in to Docker registry
        if: always()
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build Docker image
        run: |
          IMAGE="${{ steps.image-vars.outputs.IMAGE }}"
          AGENT="${{ steps.image-vars.outputs.AGENT }}"
          CONTEXT="./src/agents/${AGENT}"
          echo "Building $IMAGE from $CONTEXT"
          docker build -t "$IMAGE" "$CONTEXT"

      - name: Push Docker image
        run: |
          IMAGE="${{ steps.image-vars.outputs.IMAGE }}"
          echo "Pushing $IMAGE"
          docker push "$IMAGE"

      - name: Validate LIVEKIT_URL subdomain and livekit.toml (optional)
        run: |
          if [ -z "$LIVEKIT_URL" ]; then
            echo "LIVEKIT_URL is not set"; exit 1
          fi
          SUBDOMAIN=$(echo "$LIVEKIT_URL" | sed -E 's#^wss?://([^./]+).*#\1#')
          echo "Detected LIVEKIT_URL subdomain: $SUBDOMAIN"
          CONF="./src/agents/${{ inputs.agent }}/livekit.toml"
          if [ -f "$CONF" ]; then
            TOML_SUBDOMAIN=$(grep -E '^subdomain' "$CONF" | sed -E 's/.*= *"([^"]+)".*/\1/' || true)
            if [ -n "$TOML_SUBDOMAIN" ]; then
              echo "livekit.toml subdomain: $TOML_SUBDOMAIN"
              if [ "$SUBDOMAIN" != "$TOML_SUBDOMAIN" ]; then
                echo "ERROR: livekit.toml subdomain ($TOML_SUBDOMAIN) does not match LIVEKIT_URL subdomain ($SUBDOMAIN)"
                exit 1
              fi
            else
              echo "livekit.toml present but no subdomain key found; continuing"
            fi
          else
            echo "No livekit.toml at $CONF; ensure it exists or pass flags to lk"
          fi

      - name: Create or deploy agent (non-interactive)
        env:
          LIVEKIT_URL: ${{ secrets.STAGING_LIVEKIT_URL }}
          LIVEKIT_API_KEY: ${{ secrets.STAGING_LIVEKIT_API_KEY }}
          LIVEKIT_API_SECRET: ${{ secrets.STAGING_LIVEKIT_API_SECRET }}
        run: |
          set -euo pipefail
          AGENT="${{ steps.image-vars.outputs.AGENT }}"
          IMAGE="${{ steps.image-vars.outputs.IMAGE }}"
          AGENT_NAME="${AGENT}-staging"   # change if you prefer a different agent name
          CONF_PATH="src/agents/${AGENT}/livekit.toml"
          SUBDOMAIN=$(echo "$LIVEKIT_URL" | sed -E 's#^wss?://([^./]+).*#\1#')

          echo "Agent: $AGENT"
          echo "Agent name (used in LiveKit): $AGENT_NAME"
          echo "Image: $IMAGE"
          echo "Config: $CONF_PATH"
          echo "Project subdomain: $SUBDOMAIN"

          # Ensure lk uses env creds (no lk cloud auth)
          # If the agent already exists, deploy updated image. Otherwise create it non-interactively.
          if lk agent list --no-headers | awk '{print $1}' | grep -xq "$AGENT_NAME"; then
            echo "Agent exists — deploying new image"
            lk agent deploy "$AGENT_NAME" --image "$IMAGE" --config "$CONF_PATH"
          else
            echo "Agent not found — creating non-interactively"
            lk agent create --name "$AGENT_NAME" --image "$IMAGE" --project-subdomain "$SUBDOMAIN" --config "$CONF_PATH"
          fi

      - name: Verify deployment
        env:
          LIVEKIT_URL: ${{ secrets.STAGING_LIVEKIT_URL }}
          LIVEKIT_API_KEY: ${{ secrets.STAGING_LIVEKIT_API_KEY }}
          LIVEKIT_API_SECRET: ${{ secrets.STAGING_LIVEKIT_API_SECRET }}
        run: |
          AGENT="${{ steps.image-vars.outputs.AGENT }}"
          AGENT_NAME="${AGENT}-staging"
          echo "Checking status for $AGENT_NAME"
          lk agent status --config "src/agents/${AGENT}/livekit.toml" || lk agent status "$AGENT_NAME"
What I changed and why • Added Docker build & push steps so the agent image is available to LiveKit — the CLI will not implicitly build an image in CI without configuration. • Provide an explicit AGENT_NAME and IMAGE to
lk agent create
/
lk agent deploy
so the CLI never falls back to an interactive prompt. • Kept your original env secrets and config path usage. • Subdomain validation step to prevent the “project does not match agent subdomain” problem you saw earlier. • No TTY usage and no browser login required — the CLI uses the API key/secret from environment variables. Small notes / next steps • If you use a registry other than Docker Hub (GHCR, ECR), replace Docker login/push steps accordingly. • If you prefer a different agent naming convention, change AGENT_NAME. • If your livekit.toml lacks required fields (like
project.subdomain
), the workflow either supplies
--project-subdomain
flag during create or you should add it to the file. If you want, I can: • Convert the Docker steps to GHCR or ECR for your org, • Change the image tagging scheme (branch/tag instead of short SHA), • Or remove the build/push if you later host images externally. Tell me which registry you use (Docker Hub, GHCR, ECR) and whether you’d like a different image tag format; I’ll update the workflow accordingly.
r
Docker Hub
t
Thanks — below is a finalized GitHub Actions workflow tailored to Docker Hub. It builds the selected agent image from src/agents/<agent>, pushes it to Docker Hub, then creates the LiveKit agent non-interactively (or deploys if it already exists). This avoids the TTY/browser auth error by providing all required inputs and using environment credentials. Save as .github/workflows/deploy-staging.yml.
Copy code
name: Deploy Staging

on:
  workflow_dispatch:
    inputs:
      agent:
        description: "Agent to deploy"
        required: true
        type: choice
        options:
          - inbound
          - persistence_demo

jobs:
  deploy:
    name: Deploy ${{ inputs.agent }} to staging
    runs-on: ubuntu-latest
    env:
      # LiveKit secrets (set these in repo or org secrets)
      LIVEKIT_URL: ${{ secrets.STAGING_LIVEKIT_URL }}
      LIVEKIT_API_KEY: ${{ secrets.STAGING_LIVEKIT_API_KEY }}
      LIVEKIT_API_SECRET: ${{ secrets.STAGING_LIVEKIT_API_SECRET }}
      # Docker Hub credentials (set these as repo secrets)
      DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
      DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install lk CLI
        run: |
          curl -sSL <https://get.livekit.io/cli> | bash
          echo "$HOME/.livekit/bin" >> "$GITHUB_PATH"
          lk version

      - name: Set image name & tag
        id: image-vars
        run: |
          AGENT="${{ inputs.agent }}"
          IMAGE_NAME="${{ github.repository_owner }}/livekit-${AGENT}"
          IMAGE_TAG="${GITHUB_SHA::7}"
          IMAGE="${IMAGE_NAME}:${IMAGE_TAG}"
          echo "AGENT=${AGENT}" >> $GITHUB_OUTPUT
          echo "IMAGE=${IMAGE}" >> $GITHUB_OUTPUT
          echo "IMAGE_NAME=${IMAGE_NAME}" >> $GITHUB_OUTPUT
          echo "IMAGE_TAG=${IMAGE_TAG}" >> $GITHUB_OUTPUT

      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build Docker image
        run: |
          IMAGE="${{ steps.image-vars.outputs.IMAGE }}"
          AGENT="${{ steps.image-vars.outputs.AGENT }}"
          CONTEXT="./src/agents/${AGENT}"
          if [ ! -f "$CONTEXT/Dockerfile" ]; then
            echo "No Dockerfile found at $CONTEXT/Dockerfile"; exit 1
          fi
          echo "Building $IMAGE from $CONTEXT"
          docker build -t "$IMAGE" "$CONTEXT"

      - name: Push Docker image
        run: |
          IMAGE="${{ steps.image-vars.outputs.IMAGE }}"
          echo "Pushing $IMAGE"
          docker push "$IMAGE"

      - name: Validate LIVEKIT_URL subdomain and livekit.toml
        run: |
          if [ -z "$LIVEKIT_URL" ]; then
            echo "LIVEKIT_URL is not set"; exit 1
          fi
          SUBDOMAIN=$(echo "$LIVEKIT_URL" | sed -E 's#^wss?://([^./]+).*#\1#')
          echo "Detected LIVEKIT_URL subdomain: $SUBDOMAIN"
          CONF="./src/agents/${{ inputs.agent }}/livekit.toml"
          if [ -f "$CONF" ]; then
            TOML_SUBDOMAIN=$(grep -E '^subdomain' "$CONF" | sed -E 's/.*= *"([^"]+)".*/\1/' || true)
            if [ -n "$TOML_SUBDOMAIN" ]; then
              echo "livekit.toml subdomain: $TOML_SUBDOMAIN"
              if [ "$SUBDOMAIN" != "$TOML_SUBDOMAIN" ]; then
                echo "ERROR: livekit.toml subdomain ($TOML_SUBDOMAIN) does not match LIVEKIT_URL subdomain ($SUBDOMAIN)"
                exit 1
              fi
            else
              echo "livekit.toml present but no subdomain key found; continuing"
            fi
          else
            echo "No livekit.toml at $CONF; ensure this file exists or pass flags to lk"
          fi

      - name: Create or deploy agent (non-interactive)
        env:
          LIVEKIT_URL: ${{ secrets.STAGING_LIVEKIT_URL }}
          LIVEKIT_API_KEY: ${{ secrets.STAGING_LIVEKIT_API_KEY }}
          LIVEKIT_API_SECRET: ${{ secrets.STAGING_LIVEKIT_API_SECRET }}
        run: |
          set -euo pipefail
          AGENT="${{ steps.image-vars.outputs.AGENT }}"
          IMAGE="${{ steps.image-vars.outputs.IMAGE }}"
          AGENT_NAME="${AGENT}-staging"   # change if you prefer another name
          CONF_PATH="src/agents/${AGENT}/livekit.toml"
          SUBDOMAIN=$(echo "$LIVEKIT_URL" | sed -E 's#^wss?://([^./]+).*#\1#')

          echo "Agent: $AGENT"
          echo "Agent name (LiveKit): $AGENT_NAME"
          echo "Image: $IMAGE"
          echo "Config: $CONF_PATH"
          echo "Project subdomain: $SUBDOMAIN"

          # Confirm agent list accessible
          lk agent list --no-headers || true

          # Create or deploy without TTY. Provide --image and required flags so lk won't prompt.
          if lk agent list --no-headers | awk '{print $1}' | grep -xq "$AGENT_NAME"; then
            echo "Agent exists — deploying new image"
            lk agent deploy "$AGENT_NAME" --image "$IMAGE" --config "$CONF_PATH"
          else
            echo "Agent not found — creating non-interactively"
            lk agent create --name "$AGENT_NAME" --image "$IMAGE" --project-subdomain "$SUBDOMAIN" --config "$CONF_PATH"
          fi

      - name: Verify deployment
        env:
          LIVEKIT_URL: ${{ secrets.STAGING_LIVEKIT_URL }}
          LIVEKIT_API_KEY: ${{ secrets.STAGING_LIVEKIT_API_KEY }}
          LIVEKIT_API_SECRET: ${{ secrets.STAGING_LIVEKIT_API_SECRET }}
        run: |
          AGENT="${{ steps.image-vars.outputs.AGENT }}"
          AGENT_NAME="${AGENT}-staging"
          echo "Checking status for $AGENT_NAME"
          lk agent status --config "src/agents/${AGENT}/livekit.toml" || lk agent status "$AGENT_NAME"
Notes and recommendations • Docker Hub: ensure DOCKER_USERNAME and DOCKER_PASSWORD are set as repository secrets. • livekit.toml: if present, ensure the [project].subdomain value exactly matches the subdomain parsed from LIVEKIT_URL (no extra suffixes, different casing, or typos). • Agent name: workflow uses AGENT_NAME="${agent}-staging". Change if you want a different naming convention. • Debugging: temporarily remove or omit silent flags (none used here) to see full lk output if you need more logs. • If your agent requires additional runtime secrets (OPENAI_API_KEY, etc.), manage those separately as repository or environment secrets and inject them into the agent via `lk agent create`/`--secrets` or LiveKit dashboard. If you want, I can: • Swap Docker Hub steps to GitHub Container Registry or AWS ECR, • Change the image tag strategy to include branch names or semantic tags, • Add CI steps to inject runtime secrets into LiveKit agent creation if you want the agent to receive them automatically. Which should I do next (if anything)?