Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ config:
- changed-files:
- any-glob-to-any-file:
- 'pyproject.toml'
- 'requirements.txt'
- 'uv.lock'
- '.env.example'
- '.gitignore'

Expand Down
9 changes: 9 additions & 0 deletions .github/scripts/db.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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
Comment on lines +1 to +8
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.

echo "==> Migrations complete."
33 changes: 33 additions & 0 deletions .github/scripts/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
set -e

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

echo "==> Pulling latest code from main..."
git pull origin main
Comment on lines +1 to +7
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.


echo "==> Rebuilding API container..."
docker compose -f docker-compose.prod.yml build api

echo "==> Restarting API container..."
docker compose -f docker-compose.prod.yml up -d api

echo "==> Waiting for container to be healthy (up to 90s)..."
ITER=0
while [ $ITER -lt 12 ]; do
Comment on lines +15 to +17
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.

STATUS=$(docker inspect --format='{{.State.Health.Status}}' spoken-api 2>/dev/null || echo "starting")
echo "==> Current status: $STATUS"
if [ "$STATUS" = "healthy" ]; then
break
fi
sleep 5
ITER=$((ITER + 1))
done

STATUS=$(docker inspect --format='{{.State.Health.Status}}' spoken-api 2>/dev/null || echo "unknown")
echo "==> Container health status: $STATUS"
if [ "$STATUS" != "healthy" ]; then
echo "ERROR: Container did not become healthy in time!"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
22 changes: 12 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.11
- 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
Comment on lines +14 to 23
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.

run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
run: uv sync --frozen
- name: Lint with Black
run: black --check .
run: uv run black --check .
- name: Check imports with isort
run: isort --check-only .
run: uv run isort --check-only .
- name: Type check with Mypy
run: mypy app
run: uv run mypy app
- name: Run tests with Pytest
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/fluentmeet_test
REDIS_URL: redis://localhost:6379/1
run: |
pytest --cov=app --cov-fail-under=77 tests/
uv run pytest --cov=app --cov-fail-under=77 tests/
22 changes: 11 additions & 11 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.11
- 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"
Comment on lines +15 to +23
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.

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ruff mypy
# Install project deps to help mypy find types
pip install -r requirements.txt
run: uv sync --frozen
- name: Lint with Ruff
run: ruff check .
run: uv run ruff check .
- name: Format check with Ruff
run: ruff format --check .
run: uv run ruff format --check .
- name: Type check with Mypy
run: mypy app
run: uv run mypy app
15 changes: 11 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ FROM python:3.11-slim AS builder

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.


# System deps needed to compile psycopg2, cryptography, etc.
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc libpq-dev && \
rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# Sync dependencies to a virtual environment
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev

# ============================================================
# Stage 2 — Runtime: lean production image
Expand All @@ -28,8 +32,8 @@ RUN groupadd -r appuser && useradd -r -g appuser appuser

WORKDIR /app

# Copy installed packages from builder
COPY --from=builder /install /usr/local
# Copy the virtual environment from the builder
COPY --from=builder /build/.venv /app/.venv

# Copy application source
COPY pyproject.toml ./
Expand All @@ -47,5 +51,8 @@ EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1

# Run using the python interpreter inside the virtual environment
ENV PATH="/app/.venv/bin:$PATH"

# Run with uvicorn — 4 workers for production
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
32 changes: 14 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,10 @@ Generate a secure `SECRET_KEY` for JWT:
python -c "import secrets; print(secrets.token_hex(32))"
```

### **4. Local Development Setup**
It is highly recommended to use a virtual environment:
### 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
```
Comment on lines +86 to 90
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.


### **5. Infrastructure Setup (Docker)**
Expand All @@ -109,7 +107,8 @@ alembic upgrade headalembic upgrade head

### **Start the Backend**
```bash
uvicorn app.main:app --reload
uv run uvicorn app.main:app --reload
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
Comment on lines +110 to +111
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.

```
The API will be available at `http://localhost:8000`. You can access the interactive API documentation (Swagger UI) at `http://localhost:8000/docs`.

Expand All @@ -126,16 +125,16 @@ Ensure your models are imported in `app/models/init.py` for Alembic to detect th

### **Creating Migrations**
```bash
python -m alembic revision --autogenerate -m "Add avatar url to users"
uv run alembic revision --autogenerate -m "Add avatar url to users"
```
### **Migrate Head**
```bash
alembic upgrade head
uv run alembic upgrade head
```

### **Applying Migrations**
```bash
python -m alembic upgrade head
uv run alembic upgrade head
```

---
Expand All @@ -144,13 +143,13 @@ python -m alembic upgrade head

### **Running Tests**
```bash
pytest
uv run pytest
```

### **Test Coverage**
Generate and view a coverage report:
```bash
pytest tests/ -v --cov=app --cov-report=html --cov-report=term
uv run pytest tests/ -v --cov=app --cov-report=html --cov-report=term
# Open htmlcov/index.html in your browser
```

Expand All @@ -168,19 +167,16 @@ pytest tests/ -v --cov=app --cov-report=html --cov-report=term
## Linting & Formatting
- **Black**: Enforce consistent code formatting.
```bash
black .
uv run black .
```
- **isort**: Sort imports for readability.
```bash
isort .
uv run isort .
```
- **ruff**: Linting for code quality and style.
```bash
ruff .
```
```bash
python -m ruff check .
ruff format .
uv run ruff check .
uv run ruff format .
```

---
Expand Down
14 changes: 6 additions & 8 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: "3.8"

services:
# ── Redis ──────────────────────────────────────────────────
redis:
Expand Down Expand Up @@ -38,11 +36,11 @@ services:
volumes:
- kafka_data:/var/lib/kafka/data
healthcheck:
test: ["CMD-SHELL", "/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:29092"]
interval: 15s
timeout: 10s
retries: 5
start_period: 30s
test: ["CMD-SHELL", "/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server 127.0.0.1:29092"]
interval: 30s
timeout: 15s
retries: 10
start_period: 90s
networks:
- spoken-net

Expand All @@ -63,7 +61,7 @@ services:
redis:
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.

ports:
- "8000:8000"
healthcheck:
Expand Down
Loading
Loading