FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix: SMS queue config coercion + worker collision/retry fixes by Vishalsahani156 · Pull Request #290 · textbee/textbee · GitHub

fix: SMS queue config coercion + worker collision/retry fixes - #290

Open
Vishalsahani156 wants to merge 3 commits into
textbee:mainfrom
Vishalsahani156:fix/config-coerce-sms-queue
Open

Vishalsahani156 wants to merge 3 commits into
textbee:mainfrom
Vishalsahani156:fix/config-coerce-sms-queue

Conversation

Vishalsahani156 commented Aug 5, 2026
edited by coderabbitai Bot
Loading

Copy link
Copy Markdown
Contributor

Summary

Three related fixes for the SMS pipeline — one API-side config bug and two Android WorkManager worker bugs.

1. Coerce boolean/numeric env vars for SMS queue config (432e617)

Env vars arrive as strings, so boolean/numeric queue-config values were being read as truthy strings / NaN. Coerced them to their real types so the SMS queue config behaves as intended.

2. Key SMS status work by smsId to stop collision drops (c59f2e4)

SMSStatusUpdateWorker built its unique-work name from status + currentTimeMillis() with ExistingWorkPolicy.REPLACE. Two reports in the same millisecond (bulk sends, multipart SMS) collided on that name and the pending one was cancelled before it was ever sent — a silently lost status report. Now keyed on (smsId, status) with ExistingWorkPolicy.KEEP, which also dedupes the N duplicate broadcasts from multipart messages.

3. Fix dead retry cap + narrow catch in SMS workers (e33f669)

Same pattern in both SMSStatusUpdateWorker and SMSReceivedWorker:

  • Dead retry cap → KEY_RETRY_COUNT was written as 0 and never incremented, so retryCount >= MAX_RETRIES could never be true. Reports retried forever with exponential backoff, including permanently unrecoverable cases (401 from a revoked API key, 404 for an unknown id). Switched to WorkManager's runAttemptCount, which the framework maintains correctly across retries.
  • Narrow catch → doWork() only caught IOException. A JsonSyntaxException from a malformed 2xx body (e.g. an HTML error page from a proxy) escaped, WorkManager marked the work FAILED, and the report was lost permanently. Now caught and retried.

SMSStatusUpdateWorker already carried this fix; this brings SMSReceivedWorker in line so both workers behave identically.

Testing

  • Verified by inspection: no lingering KEY_RETRY_COUNT references, runAttemptCount is a valid Worker property, JsonSyntaxException imported.
  • Gradle build not run locally (no JDK on the dev machine) — please confirm CI compile.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved SMS processing reliability by retrying malformed responses and preventing duplicate background work.
    • Made retry limits more consistent across SMS operations.
    • Fixed SMS filter edit and delete actions after list updates.
  • Improvements
    • Configuration settings now handle boolean and numeric values more reliably, including missing or invalid entries.
    • SMS queue limits, batch sizes, and delays now use safer configuration fallbacks.

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

@Vishalsahani156 is attempting to deploy a commit to the vernu's projects Team on Vercel.

A member of the Team first needs to authorize it.

coderabbitai Bot commented Aug 5, 2026
edited
Loading

Copy link
Copy Markdown
Contributor

📝 Walkthrough

Walkthrough

The PR adds boolean and numeric configuration coercion for SMS queue settings. It updates SMS workers to use WorkManager retry counts and retry malformed JSON responses. Status updates retain status-specific unique work. RecyclerView actions use the current adapter position.

Changes

Configuration coercion and SMS worker behavior

Layer / File(s) Summary
API configuration coercion and gateway integration
api/src/common/config-coerce.ts, api/src/common/config-coerce.spec.ts, api/src/gateway/...
Adds getBool and getNumber helpers with fallback handling. SMS queue and limiter settings use the helpers. Tests cover boolean and numeric conversion.
SMS worker retry and duplicate-work handling
android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.kt, android/app/src/main/java/com/vernu/sms/workers/SMSReceivedWorker.kt
Removes persisted retry counts. Uses WorkManager’s runAttemptCount, retries JsonSyntaxException, and retains status-specific unique work.
RecyclerView action position validation
android/app/src/main/java/com/vernu/sms/activities/SMSFilterActivity.java
Edit and delete actions use getBindingAdapterPosition() and skip invalid positions.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Possibly related PRs

  • vernu/textbee#275 — Contains the same SMS worker retry, exception-handling, unique-work naming, and KEEP policy changes.
  • vernu/textbee#287 — Contains related SMS worker malformed-JSON retry and WorkManager attempt handling changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: SMS queue configuration coercion and worker collision and retry fixes.
✨ Finishing Touches 💡 1 🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Actionable comments posted: 7

🤖 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 @.env:
- Line 6: Keep NEXT_PUBLIC_API_BASE_URL as the browser-facing endpoint, add a
separate internal API URL using the textbee-api service and port 3001, and
update the server client in httpServerClient.ts to read that internal
configuration for server-side requests.

In `@android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.kt`:
- Around line 40-42: Update the uniqueWorkName construction near beginUniqueWork
to replace the System.currentTimeMillis() fallback used when smsDTO.smsId is
absent with a collision-free identifier such as workRequest.id.toString() or a
UUID, while preserving the existing SMS ID and status components.
- Around line 59-62: Update the retry guard in SMSStatusUpdateWorker so it
returns Result.failure() only when runAttemptCount is greater than MAX_RETRIES,
allowing five retries after the initial attempt while preserving the existing
maximum-retry log and failure behavior.
- Around line 78-80: Wrap the KEY_SMS_DTO Gson().fromJson(...) deserialization
in a separate try/catch for JsonSyntaxException before the API try block. Log
the malformed response using the existing TAG and return Result.retry(), while
leaving the surrounding API error handling unchanged.

In `@api/src/common/config-coerce.ts`:
- Around line 11-25: Update the boolean and numeric coercion helpers around
getNumber and the adjacent boolean getter to trim string inputs before checking
for emptiness, so whitespace-only values return fallback rather than being
parsed. Preserve direct boolean handling and valid trimmed values, and add tests
covering whitespace-only boolean and numeric configuration values.

In `@api/src/gateway/queue/sms-queue.service.ts`:
- Around line 20-24: Update the MAX_SMS_BATCH_SIZE initialization in the SMS
queue service constructor to accept only positive integer values; fall back to
100 when getNumber returns zero, a negative number, a fraction, or otherwise
invalid input, while preserving valid configured batch sizes.

In `@api/src/main.ts`:
- Around line 72-75: Update the Firebase initialization guard around
firebase.initializeApp to require firebaseConfig.projectId,
firebaseConfig.clientEmail, and firebaseConfig.privateKey before calling
firebase.credential.cert(). When any credential is absent, skip initialization
and log that the Firebase configuration is incomplete.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bb8c1b67-e98d-4de0-affd-4e30defbc553

📥 Commits

Reviewing files that changed from the base of the PR and between bf0197e and cc3f58f.

📒 Files selected for processing (14)
  • .env
  • .gitignore
  • android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.kt
  • api/.env.example
  • api/src/common/config-coerce.spec.ts
  • api/src/common/config-coerce.ts
  • api/src/gateway/gateway.module.ts
  • api/src/gateway/queue/sms-queue.service.ts
  • api/src/main.ts
  • api/test/app.e2e-spec.ts
  • api/tsconfig.json
  • docker-compose.yaml
  • web/.env.example
  • web/tsconfig.json
💤 Files with no reviewable changes (3)
  • web/tsconfig.json
  • web/.env.example
  • api/.env.example
📜 Review details ⚠️ CI failures not shown inline (1)

Commit Status: Vercel: Vercel

Conclusion: failure

Authorization required to deploy.
🧰 Additional context used 🪛 dotenv-linter (4.0.0) .env

[warning] 5-5: [UnorderedKey] The API_PORT key should go before the REDIS_PORT key

(UnorderedKey)


[warning] 6-6: [UnorderedKey] The NEXT_PUBLIC_API_BASE_URL key should go before the REDIS_PORT key

(UnorderedKey)

🔇 Additional comments (5)
docker-compose.yaml (1)

51-51: LGTM!

Also applies to: 73-73

.gitignore (1)

7-7: LGTM!

api/test/app.e2e-spec.ts (1)

3-3: LGTM!

android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.kt (2)

10-10: LGTM!

Also applies to: 22-27


40-42: 🗄️ Data Integrity & Integration

Verify ordering across status-specific work.

uniqueWorkName includes smsDTO.status, so KEEP deduplicates only matching statuses. A retry for an older status and a new request for a newer status use different names. Both can call GatewayApiService.java at Lines 25-28. If the older request finishes last, it can overwrite the newer server state.

Confirm that the gateway rejects stale transitions, or serialize work by smsId and enforce status ordering. WorkManager’s unique-work policy is scoped to each unique name. (developer.android.com)

Source: MCP tools

Comment thread .env Outdated
REDIS_PORT=6380
WEB_PORT=3005
API_PORT=3006
NEXT_PUBLIC_API_BASE_URL=http://localhost:3006/api/v1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use separate API URLs for browser and server requests.

At Line 6, http://localhost:3006/api/v1 works in the browser. The server-side client also reads this value inside textbee-web, where localhost:3006 points to the web container. Server-side API requests cannot reach textbee-api.

Keep NEXT_PUBLIC_API_BASE_URL for browser traffic. Add an internal URL such as http://textbee-api:3001/api/v1 for web/lib/httpServerClient.ts.

🧰 Tools 🪛 dotenv-linter (4.0.0)

[warning] 6-6: [UnorderedKey] The NEXT_PUBLIC_API_BASE_URL key should go before the REDIS_PORT key

(UnorderedKey)

🤖 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 @.env at line 6, Keep NEXT_PUBLIC_API_BASE_URL as the browser-facing
endpoint, add a separate internal API URL using the textbee-api service and port
3001, and update the server client in httpServerClient.ts to read that internal
configuration for server-side requests.

Comment on lines +40 to +42
val uniqueWorkName = "sms_status_${smsDTO.smsId ?: System.currentTimeMillis()}_${smsDTO.status}"
WorkManager.getInstance(context)
.beginUniqueWork(uniqueWorkName, ExistingWorkPolicy.REPLACE, workRequest)
.beginUniqueWork(uniqueWorkName, ExistingWorkPolicy.KEEP, workRequest)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- target file ---'
sed -n '1,120p' android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.kt
printf '%s\n' '--- related symbols ---'
rg -n -C 4 'SMSStatusUpdateWorker|smsDTO|beginUniqueWork|ExistingWorkPolicy' android/app/src/main/java
printf '%s\n' '--- WorkRequest construction ---'
rg -n -C 5 'OneTimeWorkRequest|workRequest|SMSStatus' android/app/src/main/java/com/vernu/sms

Repository: vernu/textbee

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
for p in Path("android").rglob("*.kt"):
    text = p.read_text(errors="ignore")
    if "SMSStatusUpdateWorker" in text or "beginUniqueWork" in text or "workRequest" in text:
        print(f"--- {p} ---")
        for i, line in enumerate(text.splitlines(), 1):
            if any(x in line for x in ("SMSStatusUpdateWorker", "beginUniqueWork", "workRequest", "smsDTO")):
                lo, hi = max(1, i-3), i+5
                lines = text.splitlines()
                for n in range(lo, min(len(lines), hi)+1):
                    print(f"{n}: {lines[n-1]}")
                print()
PY

Repository: vernu/textbee

Length of output: 33392


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- SMSDTO and status enqueue call sites ---'
rg -n -C 6 'class SMSDTO|data class SMSDTO|smsId|createSentPendingIntent|createDeliveredPendingIntent|sendTextMessage' \
  android/app/src/main/java/com/vernu/sms/dtos \
  android/app/src/main/java/com/vernu/sms/helpers/SMSHelper.kt \
  android/app/src/main/java/com/vernu/sms/receivers/SMSStatusReceiver.kt
printf '%s\n' '--- relevant project configuration/dependencies ---'
rg -n -C 2 'work-runtime|androidx.work|kotlin' android --glob 'build.gradle*' --glob 'libs.versions.toml' --glob 'gradle.properties'
printf '%s\n' '--- exact target lines ---'
sed -n '32,48p' android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.kt

Repository: vernu/textbee

Length of output: 27965


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from uuid import uuid4

def current_name(sms_id, status, now_ms, request_id):
    return f"sms_status_{sms_id if sms_id is not None else now_ms}_{status}"

same_millisecond = 1_735_000_000_000
first = current_name(None, "SENT", same_millisecond, uuid4())
second = current_name(None, "SENT", same_millisecond, uuid4())
print("timestamp fallback names equal:", first == second)
print("name:", first)

first_id = str(uuid4())
second_id = str(uuid4())
first_fixed = f"sms_status_{first_id}_SENT"
second_fixed = f"sms_status_{second_id}_SENT"
print("request-ID fallback names equal:", first_fixed == second_fixed)
print("request IDs distinct:", first_id != second_id)
PY

Repository: vernu/textbee

Length of output: 290


Use a collision-free fallback when smsDTO.smsId is absent.

Two calls within the same millisecond can generate the same uniqueWorkName. With ExistingWorkPolicy.KEEP, WorkManager ignores the second request while matching work remains pending. Use workRequest.id.toString() or a UUID for the fallback.

🤖 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 `@android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.kt`
around lines 40 - 42, Update the uniqueWorkName construction near
beginUniqueWork to replace the System.currentTimeMillis() fallback used when
smsDTO.smsId is absent with a collision-free identifier such as
workRequest.id.toString() or a UUID, while preserving the existing SMS ID and
status components.

Source: MCP tools

Comment on lines +78 to +80
} catch (e: JsonSyntaxException) {
Log.e(TAG, "Malformed response body: ${e.message}")
Result.retry()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
file="android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.kt"
printf '%s\n' "== file excerpt =="
cat -n "$file" | sed -n '1,120p'
printf '%s\n' "== Gson and worker references =="
rg -n -C 3 'SMSStatusUpdateWorker|fromJson|JsonSyntaxException|KEY_SMS_DTO|Result\.retry|execute\(' android/app/src/main/java android/app/src/test android/app/src/androidTest 2>/dev/null || true
printf '%s\n' "== declared Gson versions =="
rg -n -C 2 'com\.google\.code\.gson|gson' --glob '!*build*' .

Repository: vernu/textbee

Length of output: 29478


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' "== dependency declarations =="
find android -maxdepth 4 -type f \( -name 'build.gradle' -o -name 'build.gradle.kts' -o -name 'libs.versions.toml' -o -name 'gradle.properties' \) -print
rg -n -C 3 'gson|converter-gson|retrofit|version' android --glob 'build.gradle*' --glob 'libs.versions.toml' --glob 'gradle.properties' || true
printf '%s\n' "== SMS status service declarations =="
rg -n -C 8 'updateSMSStatus|SMSForwardResponseDTO|SMSStatus' android/app/src/main
printf '%s\n' "== DTO definitions =="
fd -i 'SMSDTO|SMSForwardResponseDTO' android/app/src/main
for f in $(fd -i 'SMSDTO|SMSForwardResponseDTO' android/app/src/main); do
  echo "== $f =="
  cat -n "$f"
done

Repository: vernu/textbee

Length of output: 34123


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re

path = Path("android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.kt")
lines = path.read_text().splitlines()

from_json = next(i for i, line in enumerate(lines, 1) if "Gson().fromJson(smsDtoJson" in line)
try_line = next(i for i, line in enumerate(lines, 1) if line.strip() == "return try {")

# This source-level verifier checks the relevant lexical ordering and catch scope.
catch_lines = [i for i, line in enumerate(lines, 1)
               if line.strip().startswith("} catch (e: JsonSyntaxException)")]
print({
    "fromJson_line": from_json,
    "api_try_line": try_line,
    "json_syntax_catch_lines": catch_lines,
    "fromJson_before_api_try": from_json < try_line,
    "fromJson_inside_api_try_by_line_range": try_line < from_json < catch_lines[-1],
})
PY

Repository: vernu/textbee

Length of output: 307


🌐 Web query:

Gson 2.9.0 Gson.fromJson(String, Class) JsonSyntaxException malformed JSON Javadoc; Retrofit 2.9.0 GsonResponseBodyConverter JsonSyntaxException execute behavior

💡 Result:

In Gson 2.9.0, the javadoc for Gson.fromJson(String, Class) states that the method throws JsonSyntaxException if the input is not a valid representation for an object of the specified class [1]. However, in practice, Gson often parses JSON leniently due to legacy design choices, meaning it may not throw a JsonSyntaxException for all forms of malformed JSON [2][3][4]. Regarding Retrofit 2.9.0, the GsonResponseBodyConverter is responsible for converting the response body using the provided Gson instance [5][6]. It executes the conversion by creating a new JsonReader and using an adapter to read the JSON stream [5]. If the JSON is syntactically invalid, the underlying Gson parsing process may throw a JsonSyntaxException, which propagates up through the Retrofit call [7][8]. Additionally, the GsonResponseBodyConverter explicitly checks if the JSON document was fully consumed; if not, it throws a JsonIOException [5]. It is important to note that when using Retrofit 2.9.0, the version of Gson included may vary depending on the dependencies configured in your project (e.g., using Gson 2.8.5 with Retrofit 2.9.0) [9]. If you encounter issues with malformed JSON not being rejected, it is generally recommended to inspect the raw response body or use strict parsing configurations (available in newer versions of Gson) if possible [3][4].

Citations:


Catch JsonSyntaxException around KEY_SMS_DTO deserialization.

Gson().fromJson(...) runs before the API try block. Gson 2.9.0 can throw JsonSyntaxException for invalid input, so the current catch does not handle rejected DTO data. Add a separate parse try/catch and log message.

🤖 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 `@android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.kt`
around lines 78 - 80, Wrap the KEY_SMS_DTO Gson().fromJson(...) deserialization
in a separate try/catch for JsonSyntaxException before the API try block. Log
the malformed response using the existing TAG and return Result.retry(), while
leaving the surrounding API error handling unchanged.

Source: MCP tools

Comment on lines +11 to +25
const raw = config.get<string | boolean>(key)
if (raw === undefined || raw === null || raw === '') return fallback
if (typeof raw === 'boolean') return raw
return String(raw).trim().toLowerCase() === 'true'
}

export function getNumber(
config: ConfigService,
key: string,
fallback: number,
): number {
const raw = config.get<string | number>(key)
if (raw === undefined || raw === null || raw === '') return fallback
const n = Number(raw)
return Number.isFinite(n) ? n : fallback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Treat whitespace-only values as absent.

A value such as MAX_SMS_BATCH_SIZE=' ' bypasses the empty check. Number(' ') returns 0 instead of the fallback. This can pass a zero batch size to SmsQueueService.

Trim string values before the empty check. Add tests for whitespace-only boolean and numeric values.

Proposed fix
-  if (raw === undefined || raw === null || raw === '') return fallback
-  if (typeof raw === 'boolean') return raw
-  return String(raw).trim().toLowerCase() === 'true'
+  if (raw === undefined || raw === null) return fallback
+  const value = typeof raw === 'string' ? raw.trim() : raw
+  if (value === '') return fallback
+  if (typeof value === 'boolean') return value
+  return String(value).toLowerCase() === 'true'
...
-  if (raw === undefined || raw === null || raw === '') return fallback
-  const n = Number(raw)
+  if (raw === undefined || raw === null) return fallback
+  const value = typeof raw === 'string' ? raw.trim() : raw
+  if (value === '') return fallback
+  const n = Number(value)
📝 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
const raw = config.get<string | boolean>(key)
if (raw === undefined || raw === null || raw === '') return fallback
if (typeof raw === 'boolean') return raw
return String(raw).trim().toLowerCase() === 'true'
}
export function getNumber(
config: ConfigService,
key: string,
fallback: number,
): number {
const raw = config.get<string | number>(key)
if (raw === undefined || raw === null || raw === '') return fallback
const n = Number(raw)
return Number.isFinite(n) ? n : fallback
const raw = config.get<string | boolean>(key)
if (raw === undefined || raw === null) return fallback
const value = typeof raw === 'string' ? raw.trim() : raw
if (value === '') return fallback
if (typeof value === 'boolean') return value
return String(value).toLowerCase() === 'true'
}
export function getNumber(
config: ConfigService,
key: string,
fallback: number,
): number {
const raw = config.get<string | number>(key)
if (raw === undefined || raw === null) return fallback
const value = typeof raw === 'string' ? raw.trim() : raw
if (value === '') return fallback
const n = Number(value)
return Number.isFinite(n) ? n : fallback
🤖 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 `@api/src/common/config-coerce.ts` around lines 11 - 25, Update the boolean and
numeric coercion helpers around getNumber and the adjacent boolean getter to
trim string inputs before checking for emptiness, so whitespace-only values
return fallback rather than being parsed. Preserve direct boolean handling and
valid trimmed values, and add tests covering whitespace-only boolean and numeric
configuration values.

Comment on lines +20 to 24
this.maxSmsBatchSize = getNumber(
this.configService,
'MAX_SMS_BATCH_SIZE',
100,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Require a positive integer batch size.

At Line 20, 0, negative values, and fractional values pass getNumber. When fcmMessages is nonempty, the loop at Line 49 never completes for 0 or negative values. This can block the queue worker.

Validate MAX_SMS_BATCH_SIZE as a positive integer. Use 100 when validation fails.

Proposed fix
-    this.maxSmsBatchSize = getNumber(
+    const maxSmsBatchSize = getNumber(
       this.configService,
       'MAX_SMS_BATCH_SIZE',
       100,
     )
+    this.maxSmsBatchSize =
+      Number.isInteger(maxSmsBatchSize) && maxSmsBatchSize > 0
+        ? maxSmsBatchSize
+        : 100
📝 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
this.maxSmsBatchSize = getNumber(
this.configService,
'MAX_SMS_BATCH_SIZE',
100,
)
const maxSmsBatchSize = getNumber(
this.configService,
'MAX_SMS_BATCH_SIZE',
100,
)
this.maxSmsBatchSize =
Number.isInteger(maxSmsBatchSize) && maxSmsBatchSize > 0
? maxSmsBatchSize
: 100
🤖 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 `@api/src/gateway/queue/sms-queue.service.ts` around lines 20 - 24, Update the
MAX_SMS_BATCH_SIZE initialization in the SMS queue service constructor to accept
only positive integer values; fall back to 100 when getNumber returns zero, a
negative number, a fraction, or otherwise invalid input, while preserving valid
configured batch sizes.

Comment thread api/src/main.ts Outdated
Comment on lines +72 to +75
if (firebaseConfig.projectId) {
firebase.initializeApp({
credential: firebase.credential.cert(firebaseConfig),
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a 'package*.json' api -x rg -n '"firebase-admin"' {}
rg -n -C 3 'FIREBASE_(PROJECT_ID|PRIVATE_KEY|CLIENT_EMAIL)|credential\.cert|initializeApp' api

Repository: vernu/textbee

Length of output: 1451


🌐 Web query:

For firebase-admin version 13.6.0, what fields does firebase.credential.cert() require when passed a service-account object?

💡 Result:

For firebase-admin version 13.6.0, the firebase.credential.cert function accepts a service-account object defined by the ServiceAccount interface [1][2]. This object requires the following three fields [3][2]: projectId (string) clientEmail (string) privateKey (string) While earlier releases of the 13.x version series initially encountered issues where the SDK expected snake_case keys (such as client_email and private_key) instead of the camelCase properties defined in the interface [4][5], these issues were resolved in version 13.0.1 and later [4][5]. Therefore, in version 13.6.0, you should use the camelCase fields as defined in the TypeScript interface [3][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- dependency declarations ---'
fd -a 'package*.json' . -x rg -n -C 2 '"firebase-admin"|"lockfileVersion"' {}

printf '%s\n' '--- Firebase configuration and initialization ---'
sed -n '50,82p' api/src/main.ts

printf '%s\n' '--- static completeness check ---'
python3 - <<'PY'
from pathlib import Path
import re

text = Path("api/src/main.ts").read_text()
config = re.search(r"const firebaseConfig = \{(.*?)\n  \}", text, re.S).group(1)
guard = re.search(r"if \((.*?)\) \{", text[text.index("const firebaseConfig"):]).group(1)
fields = re.findall(r"^\s+(\w+):", config, re.M)
print("guard:", guard)
print("config fields:", fields)
print("required credential fields present in config:",
      {name: name in fields for name in ("projectId", "clientEmail", "privateKey")})
print("guard checks all required fields:",
      all(name in guard for name in ("projectId", "clientEmail", "privateKey")))
PY

Repository: vernu/textbee

Length of output: 334


Validate all required Firebase credentials before initialization.

firebase.credential.cert() requires projectId, clientEmail, and privateKey. Check all three fields before calling firebase.initializeApp(). If any field is missing, skip Firebase initialization and log that the configuration is incomplete.

🤖 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 `@api/src/main.ts` around lines 72 - 75, Update the Firebase initialization
guard around firebase.initializeApp to require firebaseConfig.projectId,
firebaseConfig.clientEmail, and firebaseConfig.privateKey before calling
firebase.credential.cert(). When any credential is absent, skip initialization
and log that the Firebase configuration is incomplete.

Vishalsahani156 force-pushed the fix/config-coerce-sms-queue branch from cc3f58f to 432e617 Compare August 6, 2026 17:01

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

…SReceivedWorker

KEY_RETRY_COUNT was written as 0 and never incremented, so the
retryCount >= MAX_RETRIES cap was dead code and status forwards could
retry forever, including unrecoverable 401/404 responses. Switch to
WorkManager's runAttemptCount, which is maintained across retries.

Also catch JsonSyntaxException around the Retrofit response parse so a
malformed 2xx body retries instead of escaping and marking the work
permanently FAILED. Mirrors the existing fix in SMSStatusUpdateWorker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vishalsahani156 changed the title Fix/config coerce sms queue fix: SMS queue config coercion + worker collision/retry fixes Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@vernu review-request comment on #290?

Vishalsahani156 force-pushed the fix/config-coerce-sms-queue branch from bee6fc4 to e33f669 Compare August 9, 2026 16:12

This branch has not been deployed

No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant


Back | FazBrowse Home | New Git URL