Skip to content

chore: migrate to uv package manager and optimize production deployments#74

Merged
aniebietafia merged 6 commits into
mainfrom
fix/audio_transcribe_pipeline
May 23, 2026
Merged

chore: migrate to uv package manager and optimize production deployments#74
aniebietafia merged 6 commits into
mainfrom
fix/audio_transcribe_pipeline

Conversation

@aniebietafia
Copy link
Copy Markdown
Contributor

@aniebietafia aniebietafia commented May 23, 2026

  • Unify dependencies in pyproject.toml and lock versions in uv.lock using PEP 621.
  • Refactor Dockerfile to use uv sync, copy .venv to runtime stage, and run using virtual environment interpreter.
  • Update CI and Code Quality GitHub workflows to use setup-uv@v5 and execute commands via uv run.
  • Align README.md commands and labeler.yml configurations with the uv workflow.
  • Tune production Kafka healthcheck settings in docker-compose.prod.yml to use 127.0.0.1, double the timeout and retries, and increase start_period to 90s to tolerate build-time CPU/IO load.
  • Change api dependency condition on kafka from service_healthy to service_started to prevent transient deployment blockages.

Summary by CodeRabbit

  • Chores

    • Migrated dependency management from pip to uv across CI/CD pipelines, Docker builds, and development workflows.
    • Enhanced CI/CD automation with new deployment scripts for database migrations and container health checks.
    • Updated GitHub Actions workflows to use uv for Python tooling and dependency resolution.
  • Documentation

    • Updated README with new setup and command instructions using uv for consistent dependency and developer tooling management.

Review Change Stack

- Unify dependencies in pyproject.toml and lock versions in uv.lock using PEP 621.
- Refactor Dockerfile to use uv sync, copy .venv to runtime stage, and run using virtual environment interpreter.
- Update CI and Code Quality GitHub workflows to use setup-uv@v5 and execute commands via uv run.
- Align README.md commands and labeler.yml configurations with the uv workflow.
- Tune production Kafka healthcheck settings in docker-compose.prod.yml to use 127.0.0.1, double the timeout and retries, and increase start_period to 90s to tolerate build-time CPU/IO load.
- Change api dependency condition on kafka from service_healthy to service_started to prevent transient deployment blockages.

Signed-off-by: aniebietafia <aniebietafia87@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

Warning

Review limit reached

@aniebietafia, we couldn't start this review because you've used your available PR reviews for now.

Your plan currently allows 1 review/hour. Refill in 52 minutes and 16 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cfa4a327-ccb6-4e88-937f-02dc8a561a17

📥 Commits

Reviewing files that changed from the base of the PR and between c235058 and 98afc6e.

📒 Files selected for processing (1)
  • pyproject.toml
📝 Walkthrough

Walkthrough

Consolidates dependency management on uv (pyproject.toml + uv.lock), updates README, CI, and Docker to use uv, adjusts docker-compose healthchecks, and adds CI-safe Alembic migration and production redeploy scripts with health polling.

Changes

Unified Migration to uv and Deployment Automation

Layer / File(s) Summary
Dependency declaration and labeler
pyproject.toml, .github/labeler.yml
[project].dependencies filled with pinned versions and requires-python = ">=3.11"; labeler now recognizes uv.lock as a config trigger.
Developer documentation and local workflows
README.md
Local setup, backend startup, migrations, testing, and linting/formatting commands switched to uv sync and uv run.
CI/CD workflow integration
.github/workflows/ci.yml, .github/workflows/code-quality.yml
Workflows install uv, read Python version from pyproject.toml, install deps with uv sync --frozen, and run tools/tests via uv run.
Container build and runtime environment
Dockerfile
Builder copies uv, uses uv sync --frozen --no-dev; runtime copies builder venv into /app/.venv and prepends /app/.venv/bin to PATH.
Production deployment and health management
docker-compose.prod.yml, .github/scripts/db.sh, .github/scripts/run.sh
Removed top-level version; adjusted Kafka healthcheck and api depends_on; added db.sh for Alembic migrations and run.sh to rebuild/redeploy the API and poll container health, printing logs on failure.

"A rabbit nudges the lock and peeks,
uv pins the trees where dependency sleeps,
migrations run with steady cheer,
containers wake and logs appear." 🐇🌱

🎯 3 (Moderate) | ⏱️ ~22 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: migrating to uv package manager and optimizing production deployments, which are the core objectives reflected throughout the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/audio_transcribe_pipeline

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/workflows/code-quality.yml (1)

10-13: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Define least-privilege workflow permissions explicitly.

.github/workflows/code-quality.yml doesn’t define a top-level permissions: block, so it relies on default GITHUB_TOKEN permissions. Add explicit minimal permissions (e.g., contents: read) to reduce unnecessary access.

Suggested hardening
 name: Code Quality
+permissions:
+  contents: read

 on:
   push:
     branches: [ main, develop ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/code-quality.yml around lines 10 - 13, Add an explicit
top-level GitHub Actions permissions block to the workflow to enforce least
privilege; in .github/workflows/code-quality.yml, at the root of the file
(outside jobs), add a permissions: map (for the lint-and-typecheck job) with
minimal rights such as contents: read (and any other narrowly required scopes)
instead of relying on default GITHUB_TOKEN permissions so the lint-and-typecheck
job runs with least privilege.
Dockerfile (1)

6-37: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix broken venv relocation: CMD calls uvicorn console script whose shebang may still point to /build/.venv.

This Dockerfile builds the venv at /build/.venv and copies it to /app/.venv, but venv-installed console scripts typically hardcode an absolute interpreter path in their shebang, so CMD ["uvicorn", ...] (from /app/.venv/bin via PATH) can fail at startup.

Safer fix
-FROM python:3.11-slim AS builder
-WORKDIR /build
+FROM python:3.11-slim AS builder
+WORKDIR /app
@@
-COPY --from=builder /build/.venv /app/.venv
+COPY --from=builder /app/.venv /app/.venv

Or invoke module form explicitly:

-CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
+CMD ["/app/.venv/bin/python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` around lines 6 - 37, The virtualenv is built at /build/.venv then
copied to /app/.venv, but console-script shebangs (like the uvicorn wrapper
invoked by CMD ["uvicorn", ...]) will still point to /build/.venv and may break;
update the runtime usage to avoid relying on relocated shebangs by invoking
Uvicorn as a module using the venv Python (e.g., run /app/.venv/bin/python -m
uvicorn ...) or ensure shebangs are fixed after copy (rebuild or rewrite scripts
in /app/.venv/bin), and adjust the Dockerfile CMD and any PATH handling to
reference /app/.venv/bin/python -m uvicorn (or rewrite shebangs) instead of
calling the uvicorn console script directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/db.sh:
- Around line 1-8: Add a shebang (e.g., #!/usr/bin/env bash) and enable strict
modes (set -euo pipefail) at the top of the script, and validate the
DATABASE_URL_DIRECT environment variable before running migrations (fail with a
clear stderr message and non-zero exit if DATABASE_URL_DIRECT is empty or unset)
so the alembic invocation (the docker compose ... alembic -x
sqlalchemy.url="${DATABASE_URL_DIRECT}" upgrade head) cannot run against an
unintended target; keep the existing cd and docker compose usage but bail early
if the env check fails.

In @.github/scripts/run.sh:
- Around line 15-17: The printed timeout "up to 90s" is inconsistent with the
loop that currently does 12 iterations of a 5s sleep (~60s); update the
health-wait logic in .github/scripts/run.sh by either changing the loop bound
(the while condition using ITER) from 12 to 18 to actually wait ~90s, or change
the echoed message to "up to 60s" to match the existing loop; adjust the ITER
increment/sleep usage alongside the while [ $ITER -lt ... ] condition to keep
semantics correct.
- Around line 1-7: Add a POSIX shell shebang and enable strict mode (set -euo
pipefail and a safe IFS) at the top of the run.sh script, validate the target
directory before cd (checking existence and failing with a clear message), and
replace the non-deterministic "git pull origin main" with a deterministic
fetch+fast-forward/reset flow: git fetch origin main and then git -C <repo-dir>
reset --hard origin/main (or checkout and fast-forward) to avoid merges/prompts;
ensure commands use absolute paths or git -C to avoid relying on current working
directory and surface any git errors via set -e so failures abort safely.

In @.github/workflows/ci.yml:
- Around line 14-23: Replace the movable action tags with pinned commit SHAs:
locate the two uses: entries referencing "astral-sh/setup-uv@v5" and
"actions/setup-python@v5" in the CI workflow and change each to the
corresponding full immutable commit SHA for that action (e.g.,
astral-sh/setup-uv@<commit-sha> and actions/setup-python@<commit-sha>), ensuring
you pick the intended release commit so behavior remains the same and update any
lock/cache inputs if required.

In @.github/workflows/code-quality.yml:
- Around line 15-23: Replace the mutable GitHub Action tags with immutable
commit SHAs for both uses entries: change the astral-sh/setup-uv@v5 reference
(symbol: "astral-sh/setup-uv") and the actions/setup-python@v5 reference
(symbol: "actions/setup-python") to their corresponding pinned commit SHAs;
update the workflow to use those SHAs in place of `@v5` so CI runs are
reproducible and secure.

In `@docker-compose.prod.yml`:
- Line 64: The code currently sets the Kafka dependency to condition:
service_started but KafkaManager.start() performs all admin/producer/consumer
setup once and throws if Kafka is unreachable, causing Kafka initialization to
be skipped with no later retry; modify KafkaManager.start() to perform resumable
initialization with exponential backoff and bounded retries (or schedule a
background reconnect loop) that attempts admin/topic creation, producer
creation, and consumer subscriptions until success, and ensure any thrown
exceptions are propagated/logged but do not permanently mark Kafka as "skipped";
reference KafkaManager.start() for the initialization flow and
BaseConsumer._process_with_retry() as a model for retry/backoff behavior and
integrate the retry loop so the FastAPI lifespan no longer leaves consumers
unstarted when Kafka becomes available later.

In `@Dockerfile`:
- Line 9: The COPY instruction uses a mutable image reference "COPY
--from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/" which prevents reproducible
builds; update that COPY --from reference to a pinned immutable image (either a
released tag like ghcr.io/astral-sh/uv:<version> or the digest form
ghcr.io/astral-sh/uv@sha256:<digest>) so the build always pulls the exact same
image, then commit the updated Dockerfile.

In `@pyproject.toml`:
- Around line 5-117: The pyproject currently lists QA/dev tools (black,
coverage, isort, mypy, pytest, pytest-asyncio, pytest-cov, ruff) inside
[project].dependencies; remove those entries from the dependencies array and add
them under a dedicated dev dependency group (e.g. using PEP 735
[project.dependency-groups] or PEP 621 [project.optional-dependencies] with a
"dev" key) so production installs won't pull these packages; ensure you
reference the same package names (black, coverage, isort, mypy, pytest,
pytest-asyncio, pytest-cov, ruff) when creating the new dev group and keep all
other entries in the original dependencies list unchanged.

In `@README.md`:
- Around line 110-111: Remove the duplicate backend startup command in the
README by keeping only the canonical uvicorn invocation that includes host and
port; delete the redundant "uv run uvicorn app.main:app --reload" line and
retain "uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload" so
there's a single clear command shown.
- Around line 86-90: Add an explicit install step for the "uv" tool before the
existing "uv sync" instruction: update the "Local Development Setup" section to
include a short prerequisite line such as "Install uv (see
https://github.com/uvtool/uv or run your package manager install command)" and
ensure subsequent commands that use uv (e.g., "uv sync" and "uv run ...") follow
that new step so first-time users have the required tool installed.

---

Outside diff comments:
In @.github/workflows/code-quality.yml:
- Around line 10-13: Add an explicit top-level GitHub Actions permissions block
to the workflow to enforce least privilege; in
.github/workflows/code-quality.yml, at the root of the file (outside jobs), add
a permissions: map (for the lint-and-typecheck job) with minimal rights such as
contents: read (and any other narrowly required scopes) instead of relying on
default GITHUB_TOKEN permissions so the lint-and-typecheck job runs with least
privilege.

In `@Dockerfile`:
- Around line 6-37: The virtualenv is built at /build/.venv then copied to
/app/.venv, but console-script shebangs (like the uvicorn wrapper invoked by CMD
["uvicorn", ...]) will still point to /build/.venv and may break; update the
runtime usage to avoid relying on relocated shebangs by invoking Uvicorn as a
module using the venv Python (e.g., run /app/.venv/bin/python -m uvicorn ...) or
ensure shebangs are fixed after copy (rebuild or rewrite scripts in
/app/.venv/bin), and adjust the Dockerfile CMD and any PATH handling to
reference /app/.venv/bin/python -m uvicorn (or rewrite shebangs) instead of
calling the uvicorn console script directly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 06bb1df2-ea61-4993-98fb-4d2cffee25b5

📥 Commits

Reviewing files that changed from the base of the PR and between e600f97 and 02e3a4d.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • .github/labeler.yml
  • .github/scripts/db.sh
  • .github/scripts/run.sh
  • .github/workflows/ci.yml
  • .github/workflows/code-quality.yml
  • Dockerfile
  • README.md
  • docker-compose.prod.yml
  • pyproject.toml
  • requirements.txt
  • requirements/base.txt
  • requirements/dev.txt
  • requirements/prod.txt
💤 Files with no reviewable changes (1)
  • requirements.txt

Comment thread .github/scripts/db.sh
Comment on lines +1 to +8
set -e
echo "==> Navigating to project directory..."
cd ~/apps/spoken-api

echo "==> Running Alembic migrations..."
# Use -T for non-interactive shell in CI
docker compose -f docker-compose.prod.yml exec -T api \
alembic -x sqlalchemy.url="${DATABASE_URL_DIRECT}" upgrade head
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add shebang and strict env validation to prevent unsafe migration runs.

This script currently depends on caller shell semantics and does not fail fast on missing DATABASE_URL_DIRECT. That can cause execution failure or unintended migration target resolution.

Proposed fix
+#!/usr/bin/env bash
-set -e
+set -euo pipefail
+
+: "${DATABASE_URL_DIRECT:?DATABASE_URL_DIRECT is required}"

             echo "==> Navigating to project directory..."
             cd ~/apps/spoken-api
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/db.sh around lines 1 - 8, Add a shebang (e.g.,
#!/usr/bin/env bash) and enable strict modes (set -euo pipefail) at the top of
the script, and validate the DATABASE_URL_DIRECT environment variable before
running migrations (fail with a clear stderr message and non-zero exit if
DATABASE_URL_DIRECT is empty or unset) so the alembic invocation (the docker
compose ... alembic -x sqlalchemy.url="${DATABASE_URL_DIRECT}" upgrade head)
cannot run against an unintended target; keep the existing cd and docker compose
usage but bail early if the env check fails.

Comment thread .github/scripts/run.sh
Comment on lines +1 to +7
set -e

echo "==> Navigating to project directory..."
cd ~/apps/spoken-api

echo "==> Pulling latest code from main..."
git pull origin main
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the deploy script deterministic and shell-safe.

For deployment automation, add a shebang/strict mode and replace git pull with a deterministic fast-forward/reset flow to avoid merge-state drift and prompt/failure edge cases.

Proposed fix
+#!/usr/bin/env bash
-set -e
+set -euo pipefail
+
+git fetch origin main
+git reset --hard origin/main
-
-            echo "==> Pulling latest code from main..."
-            git pull origin main
+            echo "==> Synced to origin/main."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/run.sh around lines 1 - 7, Add a POSIX shell shebang and
enable strict mode (set -euo pipefail and a safe IFS) at the top of the run.sh
script, validate the target directory before cd (checking existence and failing
with a clear message), and replace the non-deterministic "git pull origin main"
with a deterministic fetch+fast-forward/reset flow: git fetch origin main and
then git -C <repo-dir> reset --hard origin/main (or checkout and fast-forward)
to avoid merges/prompts; ensure commands use absolute paths or git -C to avoid
relying on current working directory and surface any git errors via set -e so
failures abort safely.

Comment thread .github/scripts/run.sh
Comment on lines +15 to +17
echo "==> Waiting for container to be healthy (up to 90s)..."
ITER=0
while [ $ITER -lt 12 ]; do
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Health-wait window is inconsistent with the printed timeout.

The script says “up to 90s” but 12 * 5s waits about 60s. Align either the loop bound or the message to avoid misleading deploy diagnostics.

Proposed fix
-            while [ $ITER -lt 12 ]; do
+            while [ $ITER -lt 18 ]; do
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/run.sh around lines 15 - 17, The printed timeout "up to 90s"
is inconsistent with the loop that currently does 12 iterations of a 5s sleep
(~60s); update the health-wait logic in .github/scripts/run.sh by either
changing the loop bound (the while condition using ITER) from 12 to 18 to
actually wait ~90s, or change the echoed message to "up to 60s" to match the
existing loop; adjust the ITER increment/sleep usage alongside the while [ $ITER
-lt ... ] condition to keep semantics correct.

Comment thread .github/workflows/ci.yml
Comment on lines +14 to 23
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
python-version-file: "pyproject.toml"
- name: Install dependencies
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin GitHub Actions to immutable commit SHAs.
In .github/workflows/ci.yml (lines 15 and 20), astral-sh/setup-uv@v5 and actions/setup-python@v5 use movable @v5 tags; pin both uses: entries to full commit SHAs to avoid unexpected upstream changes.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 15-15: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 14 - 23, Replace the movable action
tags with pinned commit SHAs: locate the two uses: entries referencing
"astral-sh/setup-uv@v5" and "actions/setup-python@v5" in the CI workflow and
change each to the corresponding full immutable commit SHA for that action
(e.g., astral-sh/setup-uv@<commit-sha> and actions/setup-python@<commit-sha>),
ensuring you pick the intended release commit so behavior remains the same and
update any lock/cache inputs if required.

Comment on lines +15 to +23
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
python-version-file: "pyproject.toml"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin GitHub Actions to immutable commit SHAs (not mutable @v5 tags).
In .github/workflows/code-quality.yml, astral-sh/setup-uv@v5 (line 16) and actions/setup-python@v5 (line 21) use mutable tags; pin them to commit SHAs for reproducible and safer CI runs.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 16-16: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 21-21: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/code-quality.yml around lines 15 - 23, Replace the mutable
GitHub Action tags with immutable commit SHAs for both uses entries: change the
astral-sh/setup-uv@v5 reference (symbol: "astral-sh/setup-uv") and the
actions/setup-python@v5 reference (symbol: "actions/setup-python") to their
corresponding pinned commit SHAs; update the workflow to use those SHAs in place
of `@v5` so CI runs are reproducible and secure.

Comment thread docker-compose.prod.yml
condition: service_healthy
kafka:
condition: service_healthy
condition: service_started
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect startup-time Kafka usage and retry/backoff handling.
rg -n -C3 --iglob '*.py' '\b(KAFKA_BOOTSTRAP_SERVERS|AIOKafka|KafkaProducer|KafkaConsumer|bootstrap_servers)\b'
rg -n -C3 --iglob '*.py' '\b(retry|backoff|tenacity|sleep\(|wait_for|retrying)\b'

Repository: Brints/spoken-api

Length of output: 12576


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Confirm depends_on condition at the referenced line
echo "== docker-compose.prod.yml around line 64 =="
nl -ba docker-compose.prod.yml | sed -n '50,80p'

# 2) Inspect API startup/lifespan hook where KafkaManager.start() is called (if present)
echo "== Searching for KafkaManager / get_kafka_manager usage =="
rg -n --hidden --iglob 'app/**/*.py' '\bget_kafka_manager\b|\bKafkaManager\b|kafka_manager\.start\(' app || true

# 3) Inspect KafkaManager.start() and initialization flow
echo "== app/kafka/manager.py relevant sections =="
nl -ba app/kafka/manager.py | sed -n '1,260p'

# 4) Inspect producer/consumer startup methods for retry/backoff
echo "== app/kafka/producer.py relevant sections =="
nl -ba app/kafka/producer.py | sed -n '1,220p'

echo "== app/kafka/consumer.py relevant sections =="
nl -ba app/kafka/consumer.py | sed -n '1,260p'

# 5) Inspect exception handling around Kafka startup in FastAPI/main (if found)
echo "== Searching for FastAPI lifespan / startup blocks =="
rg -n --hidden --iglob 'app/**/*.py' '\blifespan\b|startup\b|on_startup\b' app || true

# If app/main.py exists, show it (only if small enough)
if [ -f app/main.py ]; then
  echo "== app/main.py =="
  wc -l app/main.py
  nl -ba app/main.py | sed -n '1,260p'
fi

Repository: Brints/spoken-api

Length of output: 167


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# helper: print file with line numbers (works without nl)
print_with_numbers() {
  local file="$1"
  awk '{printf "%6d:%s\n", NR, $0}' "$file"
}

echo "== docker-compose.prod.yml around line 64 =="
if [ -f docker-compose.prod.yml ]; then
  print_with_numbers docker-compose.prod.yml | sed -n '50,80p'
else
  echo "Missing docker-compose.prod.yml"
fi

echo "== Searching for KafkaManager / get_kafka_manager usage =="
rg -n --hidden --iglob 'app/**/*.py' '\bget_kafka_manager\b|\bKafkaManager\b|kafka_manager\.start\(' app || true
rg -n --hidden --iglob 'app/**/*.py' '\b(lifespan|on_startup|startup)\b' app || true

echo "== app/kafka/manager.py =="
if [ -f app/kafka/manager.py ]; then
  wc -l app/kafka/manager.py
  # show the start/init path only
  rg -n 'class KafkaManager|async def start|_init_topics|AIOKafkaAdminClient' app/kafka/manager.py
  print_with_numbers app/kafka/manager.py | sed -n '1,260p'
else
  echo "Missing app/kafka/manager.py"
fi

echo "== app/kafka/producer.py =="
if [ -f app/kafka/producer.py ]; then
  wc -l app/kafka/producer.py
  rg -n 'class KafkaProducer|async def start|def is_started' app/kafka/producer.py
  print_with_numbers app/kafka/producer.py | sed -n '1,220p'
else
  echo "Missing app/kafka/producer.py"
fi

echo "== app/kafka/consumer.py =="
if [ -f app/kafka/consumer.py ]; then
  wc -l app/kafka/consumer.py
  rg -n 'class BaseConsumer|async def start|_process_with_retry|async def _process_with_retry' app/kafka/consumer.py
  print_with_numbers app/kafka/consumer.py | sed -n '1,320p'
else
  echo "Missing app/kafka/consumer.py"
fi

# Inspect FastAPI entrypoint files if present
for f in app/main.py main.py app/app.py server.py; do
  if [ -f "$f" ]; then
    echo "== Inspecting $f =="
    wc -l "$f"
    rg -n --hidden '\blifespan\b|on_startup|startup|KafkaManager|get_kafka_manager|kafka_manager\.start\(' "$f" || true
    print_with_numbers "$f" | sed -n '1,260p'
  fi
done

Repository: Brints/spoken-api

Length of output: 31198


Handle Kafka not-ready startup explicitly when using service_started.

docker-compose.prod.yml switches Kafka dependency to condition: service_started, but KafkaManager.start() performs topic/admin, producer, and consumer initialization with no startup retry/backoff—if Kafka isn’t reachable, startup throws and Kafka initialization is skipped (FastAPI lifespan catches the exception and logs “Kafka startup skipped”). There’s no later background retry to start Kafka, so the API can come up without running consumers; message retry/backoff only exists for already-consuming message handling (BaseConsumer._process_with_retry()).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.prod.yml` at line 64, The code currently sets the Kafka
dependency to condition: service_started but KafkaManager.start() performs all
admin/producer/consumer setup once and throws if Kafka is unreachable, causing
Kafka initialization to be skipped with no later retry; modify
KafkaManager.start() to perform resumable initialization with exponential
backoff and bounded retries (or schedule a background reconnect loop) that
attempts admin/topic creation, producer creation, and consumer subscriptions
until success, and ensure any thrown exceptions are propagated/logged but do not
permanently mark Kafka as "skipped"; reference KafkaManager.start() for the
initialization flow and BaseConsumer._process_with_retry() as a model for
retry/backoff behavior and integrate the retry loop so the FastAPI lifespan no
longer leaves consumers unstarted when Kafka becomes available later.

Comment thread Dockerfile
WORKDIR /build

# Copy uv binary from the official image
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show Dockerfile around the referenced lines
if [ -f Dockerfile ]; then
  echo "== Dockerfile (lines 1-40) =="
  nl -ba Dockerfile | sed -n '1,40p'
else
  echo "Dockerfile not found at repo root"
  exit 1
fi

# Find all occurrences of the uv image/tag
echo "== Search for ghcr.io/astral-sh/uv =="
rg -n "ghcr\.io/astral-sh/uv(?::[^ \t]+)?" . || true

echo "== Search for :latest usage =="
rg -n "ghcr\.io/astral-sh/uv:latest" . || true

Repository: Brints/spoken-api

Length of output: 152


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show Dockerfile around the referenced lines
if [ -f Dockerfile ]; then
  echo "== Dockerfile (lines 1-60) =="
  cat -n Dockerfile | sed -n '1,60p'
else
  echo "Dockerfile not found at repo root"
  exit 1
fi

echo "== Search for ghcr.io/astral-sh/uv =="
rg -n "ghcr\.io/astral-sh/uv" . || true

echo "== Search for ghcr.io/astral-sh/uv:latest specifically =="
rg -n "ghcr\.io/astral-sh/uv:latest" . || true

echo "== Search for any COPY --from=ghcr.io/astral-sh/uv:... =="
rg -n "COPY\s+--from=ghcr\.io/astral-sh/uv:[^\s]+" Dockerfile || true

Repository: Brints/spoken-api

Length of output: 2608


🌐 Web query:

ghcr.io/astral-sh/uv latest tag mutable digest pin

💡 Result:

ghcr.io/astral-sh/uv:latest tag is intended as a “floating”/convenience tag, so it should be treated as mutable; for reproducibility you should pin by image digest (or at least by a specific version tag like:0.11.16). The uv Docker docs explicitly recommend pinning to a specific uv version and state that pinning to a specific SHA256 digest is best practice, because tags can be moved across different commit SHAs [1]. Practical ways to “digest pin”: 1) Pin by digest directly in Dockerfile/COPY, e.g. replace:latest with ghcr.io/astral-sh/uv@sha256: (docs show this pattern for the image) [1]. 2) Derive the digest for:latest at build/CI time using a registry query tool, then use that digest for the actual copy/deploy step (the docs show gh attestation verify resolving an OCI reference to a digest for verification purposes, e.g. “Resolved digest sha256:…” for oci://ghcr.io/astral-sh/uv:latest) [1]. Note: your exact digest for today’s:latest is not stated in the sources we found; you must resolve it from GHCR for your target platform (the registry tags usually map to an image manifest/index that has a digest) [1].

Citations:


Pin ghcr.io/astral-sh/uv to an immutable digest (Dockerfile line 9)
Dockerfile uses COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/; :latest is mutable, so pin to a specific version tag or ghcr.io/astral-sh/uv@sha256:<digest> for reproducible/auditable builds.

COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` at line 9, The COPY instruction uses a mutable image reference
"COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/" which prevents
reproducible builds; update that COPY --from reference to a pinned immutable
image (either a released tag like ghcr.io/astral-sh/uv:<version> or the digest
form ghcr.io/astral-sh/uv@sha256:<digest>) so the build always pulls the exact
same image, then commit the updated Dockerfile.

Comment thread pyproject.toml
Comment on lines +5 to +117
dependencies = [
"aioboto3==15.5.0",
"aiobotocore==2.25.1",
"aiofiles==25.1.0",
"aiohappyeyeballs==2.6.1",
"aiohttp==3.13.3",
"aioitertools==0.13.0",
"aiokafka==0.13.0",
"aiosignal==1.4.0",
"alembic==1.18.4",
"annotated-doc==0.0.4",
"annotated-types==0.7.0",
"anyio==4.12.1",
"async-timeout==5.0.1",
"asyncpg==0.31.0",
"attrs==25.4.0",
"bcrypt==5.0.0",
"black==26.3.1",
"boto3==1.40.61",
"botocore==1.40.61",
"certifi==2026.2.25",
"cffi==2.0.0",
"charset-normalizer==3.4.5",
"click==8.3.1",
"cloudinary==1.44.1",
"colorama==0.4.6",
"coverage==7.13.4",
"cryptography==46.0.5",
"deepgram-sdk==6.0.1",
"deprecated==1.3.1",
"distro==1.9.0",
"dnspython==2.8.0",
"ecdsa==0.19.1",
"email-validator==2.3.0",
"fastapi==0.135.1",
"fastapi-cli==0.0.24",
"fastapi-cloud-cli==0.15.0",
"fastar==0.8.0",
"frozenlist==1.8.0",
"greenlet==3.3.2",
"h11==0.16.0",
"hiredis==3.3.0",
"httpcore==1.0.9",
"httptools==0.7.1",
"httpx==0.28.1",
"idna==3.11",
"iniconfig==2.3.0",
"isort==8.0.1",
"itsdangerous==2.2.0",
"jinja2==3.1.6",
"jiter==0.13.0",
"jmespath==1.1.0",
"librt==0.8.1",
"limits==5.8.0",
"mailgun2==2.0.1",
"mako==1.3.10",
"markdown-it-py==4.0.0",
"markupsafe==3.0.3",
"mdurl==0.1.2",
"multidict==6.7.1",
"mypy==1.19.1",
"mypy-extensions==1.1.0",
"openai==2.26.0",
"packaging==26.0",
"passlib==1.7.4",
"pathspec==1.0.4",
"platformdirs==4.9.4",
"pluggy==1.6.0",
"propcache==0.4.1",
"psycopg2-binary==2.9.11",
"pyasn1==0.6.2",
"pycparser==3.0",
"pydantic==2.12.5",
"pydantic-core==2.41.5",
"pydantic-extra-types==2.11.0",
"pydantic-settings==2.13.1",
"pygments==2.19.2",
"pytest==9.0.2",
"pytest-asyncio==1.3.0",
"pytest-cov==7.0.0",
"python-dateutil==2.9.0.post0",
"python-dotenv==1.2.2",
"python-jose==3.5.0",
"python-multipart==0.0.22",
"pytokens==0.4.1",
"pyyaml==6.0.3",
"redis==7.4.0",
"requests==2.32.5",
"resend==2.23.0",
"rich==14.3.3",
"rich-toolkit==0.19.7",
"rignore==0.7.6",
"rsa==4.9.1",
"ruff==0.15.6",
"s3transfer==0.14.0",
"sentry-sdk==2.54.0",
"shellingham==1.5.4",
"six==1.17.0",
"slowapi==0.1.9",
"sniffio==1.3.1",
"sqlalchemy==2.0.48",
"starlette==0.52.1",
"tqdm==4.67.3",
"typer==0.24.1",
"typing-extensions==4.15.0",
"typing-inspection==0.4.2",
"urllib3==2.6.3",
"uvicorn==0.41.0",
"watchfiles==1.1.1",
"websockets==16.0",
"wrapt==1.17.3",
"yarl==1.23.0",
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Dev/test tools currently in [project].dependencies:"
rg -n '^\s*"(black|coverage|isort|mypy|pytest|pytest-asyncio|pytest-cov|ruff)==.*"' pyproject.toml

echo "Check whether a dedicated dev group exists:"
rg -n '^\[(dependency-groups|project\.optional-dependencies)\]' pyproject.toml || true

Repository: Brints/spoken-api

Length of output: 361


Split dev/test tooling out of [project].dependencies.
pyproject.toml currently includes black, coverage, isort, mypy, pytest, pytest-asyncio, pytest-cov, and ruff in [project].dependencies, and there’s no [dependency-groups] / [project.optional-dependencies] section to exclude them. Move these into a dev dependency group (PEP 735) so production installs don’t pull QA/test tools.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` around lines 5 - 117, The pyproject currently lists QA/dev
tools (black, coverage, isort, mypy, pytest, pytest-asyncio, pytest-cov, ruff)
inside [project].dependencies; remove those entries from the dependencies array
and add them under a dedicated dev dependency group (e.g. using PEP 735
[project.dependency-groups] or PEP 621 [project.optional-dependencies] with a
"dev" key) so production installs won't pull these packages; ensure you
reference the same package names (black, coverage, isort, mypy, pytest,
pytest-asyncio, pytest-cov, ruff) when creating the new dev group and keep all
other entries in the original dependencies list unchanged.

Comment thread README.md
Comment on lines +86 to 90
### 4. Local Development Setup
It is highly recommended to use the `uv` tool for fast virtual environment and dependency management:
```bash
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txt
uv sync
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show README.md around the reported lines
echo "---- README.md: lines 70-120 ----"
nl -ba README.md | sed -n '70,120p'

# Find all mentions of uv
echo "---- rg 'uv' in README.md ----"
rg -n "(\buv\b)" README.md || true

# Look for any install/setup instructions for uv
echo "---- rg install/setup keywords for uv ----"
rg -n "(install|setup|prerequisite|requires|tooling).*uv|uv.*(install|setup|prerequisite)|curl.*uv|brew.*uv|pipx.*uv" README.md || true

Repository: Brints/spoken-api

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- README.md: lines 70-120 ----"
awk 'NR>=70 && NR<=120 {printf "%d\t%s\n", NR, $0}' README.md

echo "---- rg 'uv' in README.md ----"
rg -n "(\buv\b)" README.md || true

echo "---- rg install/setup keywords around uv ----"
rg -n "(install|setup|prerequisite|requires|tooling).{0,40}uv|uv.{0,40}(install|setup|prerequisite)|curl.{0,40}uv|brew.{0,40}uv|pipx.{0,40}uv" README.md || true

Repository: Brints/spoken-api

Length of output: 2144


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- README.md: lines 70-120 ----"
awk 'NR>=70 && NR<=120 {printf "%d\t%s\n", NR, $0}' README.md

echo "---- rg 'uv' in README.md ----"
rg -n "(\buv\b)" README.md || true

echo "---- rg install/setup keywords around uv ----"
rg -n "(install|setup|prerequisite|requires|tooling).{0,40}uv|uv.{0,40}(install|setup|prerequisite)|curl.{0,40}uv|brew.{0,40}uv|pipx.{0,40}uv" README.md || true

Repository: Brints/spoken-api

Length of output: 2144


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- README.md: lines 1-120 ----"
awk 'NR>=1 && NR<=120 {printf "%d\t%s\n", NR, $0}' README.md

echo "---- README.md: lines 120-210 ----"
awk 'NR>=120 && NR<=210 {printf "%d\t%s\n", NR, $0}' README.md

echo "---- README.md: lines 210-260 ----"
awk 'NR>=210 && NR<=260 {printf "%d\t%s\n", NR, $0}' README.md

echo "---- rg 'uv' in README.md (full match context) ----"
rg -n --context=2 "\buv\b" README.md || true

Repository: Brints/spoken-api

Length of output: 9579


Add an explicit “Install uv” step before running uv sync in the Local Development Setup.

The README recommends uv sync but doesn’t include any prerequisite/install instructions for uv (even though other steps rely on uv run ...). Add a short install step (or link) so first-time users don’t fail on setup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 86 - 90, Add an explicit install step for the "uv"
tool before the existing "uv sync" instruction: update the "Local Development
Setup" section to include a short prerequisite line such as "Install uv (see
https://github.com/uvtool/uv or run your package manager install command)" and
ensure subsequent commands that use uv (e.g., "uv sync" and "uv run ...") follow
that new step so first-time users have the required tool installed.

Comment thread README.md
Comment on lines +110 to +111
uv run uvicorn app.main:app --reload
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove duplicate backend start command.

Line 110 and Line 111 document two startup commands for the same purpose; keep one canonical command to avoid confusion.

Doc cleanup
-uv run uvicorn app.main:app --reload
 uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uv run uvicorn app.main:app --reload
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 110 - 111, Remove the duplicate backend startup
command in the README by keeping only the canonical uvicorn invocation that
includes host and port; delete the redundant "uv run uvicorn app.main:app
--reload" line and retain "uv run uvicorn app.main:app --host 0.0.0.0 --port
8000 --reload" so there's a single clear command shown.

…toml

- Merge remote branch 'origin/main' into fix/audio_transcribe_pipeline.
- Remove duplicate `version` field from the bottom of the `[project.dependencies]` block in `pyproject.toml` (introduced by Git's 3-way merge alignment) to prevent setup-python workflow parser crashes.
- Maintain version "1.18.8" at the top of the project metadata.

Signed-off-by: aniebietafia <aniebietafia87@gmail.com>
Signed-off-by: aniebietafia <aniebietafia87@gmail.com>
Update [tool.isort] configuration in pyproject.toml to ignore virtual environment directories, preventing CI/CD quality check failures from scanning third-party site-packages.

Signed-off-by: aniebietafia <aniebietafia87@gmail.com>
@aniebietafia aniebietafia merged commit 7085392 into main May 23, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant