使用外部共享 Redis 部署时,若密码包含 /、= 等特殊字符,按 URL 规范只能以百分号编码写入 REDIS_URL(明文会破坏 URL 解析)。当前版本下这样的配置会导致容器启动即崩溃。#1312
Conversation
使用外部共享 Redis 部署时,若密码包含 /、= 等特殊字符,按 URL 规范只能以百分号编码写入 REDIS_URL(明文会破坏 URL 解析)。当前版本下这样的配置会导致容器启动即崩溃。
问题复现
1. 配置 REDIS_URL=redis://:pass%2Fword%3D@host:6379/15(密码原文为 pass/word=)
2. 启动服务,日志出现:
ERR action=schedule_auto_cleanup_error error=WRONGPASS invalid username-password pair
ReplyError: WRONGPASS ... command: { name: 'auth', args: [ 'pass%2Fword%3D' ] }
fatal msg=[Lifecycle] unhandledRejection
2. AUTH 参数里是未解码的 %2F/%3D 原文。
3. 另外:即使认证通过,URL 中 /15 的 DB 序号也会被忽略,Bull 键全部写入 DB 0。
根因
src/lib/notification/notification-queue.ts 与 src/lib/log-cleanup/cleanup-queue.ts 用 new URL() 解析后直接取 url.password/url.username。WHATWG URL 不会自动解码 userinfo,且代码未读取 url.pathname 中的 DB 序号。主客户端(src/lib/redis/client.ts)走 ioredis 内置 parseURL,已正确解码并支持 db,两条路径行为不一致。
改动
两处队列的 URL 解析:decodeURIComponent userinfo + 从 pathname 解析 db 传入 Bull redis options。共 +16/−4 行,无行为面扩张。
验证
- 实机复现环境(密码含 / 与 = 的共享 Redis + /15 DB 选择):修复前 AUTH 失败进程崩溃;修复后认证通过、Bull 键落在 DB 15。
- (冒烟测试的具体输出,等本机验证完成后填入)
📝 WalkthroughWalkthrough修改了两个Redis队列初始化文件(日志清理队列与通知队列),对REDIS_URL中的用户名和密码增加decodeURIComponent解码处理,并新增从URL pathname解析数据库索引编号并设置redisQueueOptions.db的逻辑。 ChangesRedis URL解析修复
Estimated code review effort: 2 (Simple) | ~10 minutes Related Issues: 未在提供信息中说明。 Related PRs: 未在提供信息中说明。 Suggested labels: bug, redis Suggested reviewers: 未在提供信息中说明。 Poem 兔子敲键盘,解码字符不再乱, 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces URL decoding for Redis credentials and supports selecting a database index from the URL path in both the log cleanup and notification queues. The reviewer identified a critical issue where empty username or password strings will cause connection failures on unauthenticated Redis instances due to empty AUTH commands. It is recommended to only assign these options if they are non-empty, and to extract this duplicated parsing logic into a shared utility.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| redisQueueOptions.password = decodeURIComponent(url.password); | ||
| redisQueueOptions.username = decodeURIComponent(url.username); // 传递用户名 |
There was a problem hiding this comment.
Same
decodeURIComponent / malformed-encoding caveat as cleanup-queue
url.password can contain a structurally valid but semantically malformed percent sequence (e.g. %GG) that new URL() accepts but decodeURIComponent rejects with URIError. The outer catch re-throws as "Invalid REDIS_URL format", which is caught further up and only emits a warning — the initialization failure may be non-obvious to operators. Same hardening suggestion as in cleanup-queue.ts.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/notification/notification-queue.ts
Line: 193-194
Comment:
**Same `decodeURIComponent` / malformed-encoding caveat as cleanup-queue**
`url.password` can contain a structurally valid but semantically malformed percent sequence (e.g. `%GG`) that `new URL()` accepts but `decodeURIComponent` rejects with `URIError`. The outer catch re-throws as `"Invalid REDIS_URL format"`, which is caught further up and only emits a warning — the initialization failure may be non-obvious to operators. Same hardening suggestion as in `cleanup-queue.ts`.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/lib/log-cleanup/cleanup-queue.ts (1)
49-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win解码与 DB 解析逻辑正确,但与
notification-queue.ts完全重复。
decodeURIComponent解码 userinfo、Number.isNaN守卫下的 pathname DB 解析都正确,且整体位于try/catch中,decodeURIComponent对畸形百分号编码抛出的URIError也会被捕获。这段 Redis URL 解析逻辑与
src/lib/notification/notification-queue.ts(Line 189-199) 几乎逐字重复。建议抽取到一个共享工具函数(例如parseRedisUrlToOptions(redisUrl, useTls)),避免两处将来出现分叉。♻️ 建议抽取共享辅助函数
// src/lib/redis/parse-redis-url.ts export function parseRedisUrlToOptions( redisUrl: string, useTls: boolean ): Queue.QueueOptions["redis"] { const options: Queue.QueueOptions["redis"] = {}; const url = new URL(redisUrl); options.host = url.hostname; options.port = parseInt(url.port || "6379", 10); options.password = decodeURIComponent(url.password); options.username = decodeURIComponent(url.username); const dbFromPath = parseInt(url.pathname.slice(1), 10); if (!Number.isNaN(dbFromPath)) { options.db = dbFromPath; } if (useTls) { const rejectUnauthorized = process.env.REDIS_TLS_REJECT_UNAUTHORIZED !== "false"; options.tls = { host: url.hostname, servername: url.hostname, rejectUnauthorized, }; } return options; }🤖 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 `@src/lib/log-cleanup/cleanup-queue.ts` around lines 49 - 56, The Redis URL parsing in cleanup-queue is duplicated in notification-queue, so extract the shared logic into a reusable helper such as parseRedisUrlToOptions(redisUrl, useTls) and have both call sites use it. Move the existing WHATWG URL handling, decodeURIComponent userinfo decoding, DB parsing with Number.isNaN guard, and TLS option setup into the shared function so the behavior stays identical while avoiding future drift.
🤖 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.
Nitpick comments:
In `@src/lib/log-cleanup/cleanup-queue.ts`:
- Around line 49-56: The Redis URL parsing in cleanup-queue is duplicated in
notification-queue, so extract the shared logic into a reusable helper such as
parseRedisUrlToOptions(redisUrl, useTls) and have both call sites use it. Move
the existing WHATWG URL handling, decodeURIComponent userinfo decoding, DB
parsing with Number.isNaN guard, and TLS option setup into the shared function
so the behavior stays identical while avoiding future drift.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 70496fb3-b366-464b-b94e-8f3169503078
📒 Files selected for processing (2)
src/lib/log-cleanup/cleanup-queue.tssrc/lib/notification/notification-queue.ts
There was a problem hiding this comment.
Code Review Summary
Clean, correct, well-commented fix for a real production crash. The Bull queues in cleanup-queue.ts and notification-queue.ts read url.password/url.username raw (the WHATWG URL API does not percent-decode userinfo) and ignored the /N db index, so a REDIS_URL whose password contains /, =, etc. — only representable via percent-encoding — failed AUTH and crashed the process, and the db index was always dropped. The change aligns these two queue paths with the main client (src/lib/redis/client.ts, which hands the raw URL to ioredis' built-in parseURL). No correctness, security, type, or error-handling defects were found in the new code. Two lower-severity items below.
PR Size: XS
- Lines changed: 20 (+16 / -4)
- Files changed: 2
Issues Found
| Category | Critical | High | Medium | Low |
|---|---|---|---|---|
| Logic/Bugs | 0 | 0 | 0 | 0 |
| Security | 0 | 0 | 0 | 0 |
| Error Handling | 0 | 0 | 0 | 0 |
| Types | 0 | 0 | 0 | 0 |
| Comments/Docs | 0 | 0 | 0 | 0 |
| Tests | 0 | 0 | 1 | 0 |
| Simplification | 0 | 0 | 0 | 1 |
Critical Issues (Must Fix)
None.
High Priority Issues (Should Fix)
None.
Medium / Low Findings
- [Tests — Medium] New
decodeURIComponent+db-selection logic has no unit coverage, and the same ~14-line parse block is duplicated verbatim in both queue files (this PR had to apply the same fix twice). Recommend extracting a purebuildBullRedisOptions(redisUrl)helper and testing it — this kills both birds. See the inline comment onsrc/lib/log-cleanup/cleanup-queue.ts. CLAUDE.md requires >=80% coverage for new features, andtests/unit/lib/redis/client.test.tsalready tests analogous URL handling for the main client. - [Process — Low] CLAUDE.md states
PR Target Branch: dev (all pull requests must target the dev branch), but this PR targetsmain. Worth confirming whether it should be retargeted todev.
Notes
decodeURIComponentcan throwURIErroron a malformed%sequence, but the new lines sit inside the existingtry/catch(cleanup-queue.ts:61 / notification-queue.ts:204), which logs and re-throwsInvalid REDIS_URL format— so this is not a silent/uncaught failure. No change needed.- Pre-merge, run the CLAUDE.md checklist:
bun run build && bun run lint && bun run typecheck && bun run test.
Review Coverage
- Logic and correctness
- Security (OWASP Top 10)
- Error handling
- Type safety
- Documentation accuracy
- Test coverage
- Code clarity
Automated review by Claude AI
Summary
Fixes a container startup crash when
REDIS_URLcontains percent-encoded userinfo (passwords with/,=,@, etc.) and restores DB-index selection from the URL path for the Bull-backed cleanup and notification queues. Aligns the two queue code paths with the main Redis client (src/lib/redis/client.ts), which already decodes credentials and honors the DB index via ioredis' built-inparseURL.Related Issues / PRs
cleanup-queue.ts) that this change builds on and fixes a decoding/DB-index gap in问题复现
REDIS_URL=redis://:pass%2Fword%3D@host:6379/15(密码原文为pass/word=)ERR action=schedule_auto_cleanup_error error=WRONGPASS invalid username-password pair ReplyError: WRONGPASS ... command: { name: 'auth', args: [ 'pass%2Fword%3D' ] } fatal msg=[Lifecycle] unhandledRejection%2F/%3D原文。/15的 DB 序号也会被忽略,Bull 键全部写入 DB 0。根因
src/lib/notification/notification-queue.ts与src/lib/log-cleanup/cleanup-queue.ts用new URL()解析后直接取url.password/url.username。WHATWG URL 不会自动解码 userinfo,且代码未读取url.pathname中的 DB 序号。主客户端(src/lib/redis/client.ts)走 ioredis 内置parseURL,已正确解码并支持 db,两条路径行为不一致。改动
两处队列的 URL 解析:
decodeURIComponentuserinfo + 从 pathname 解析 db 传入 Bull redis options。共 +16/−4 行,无行为面扩张。src/lib/log-cleanup/cleanup-queue.tsdecodeURIComponentuserinfo;从url.pathname解析 DB 序号src/lib/notification/notification-queue.tsdecodeURIComponentuserinfo;从url.pathname解析 DB 序号无新增自动化测试(改动为两处对称的解析逻辑,作者已做实机验证)。
验证
/与=的共享 Redis +/15DB 选择):修复前 AUTH 失败进程崩溃;修复后认证通过、Bull 键落在 DB 15。Description enhanced by Claude AI
Greptile Summary
Fixes a startup crash on
REDIS_URLs with percent-encoded credentials (passwords containing/,=,@, etc.) and adds DB-index selection from the URL path for the two Bull-backed queues. Both queue files previously passed rawurl.password/url.usernamedirectly to ioredis without decoding, causing AUTH failures; this aligns them withsrc/lib/redis/client.ts.cleanup-queue.tsandnotification-queue.ts:decodeURIComponentis now applied to bothurl.passwordandurl.usernamebefore they are passed to Bull's redis options, andurl.pathnameis parsed to populateredisQueueOptions.dbwhen a DB index is present (e.g./15).Confidence Score: 5/5
Safe to merge — the two changed files apply a narrow, symmetric fix that closes a real connection failure without touching any logic beyond URL credential parsing.
Both changes are minimal and correct: decodeURIComponent on userinfo fields is the standard fix for WHATWG URL's non-decoding behavior, and reading url.pathname for the DB index matches how the main Redis client already works. Empty-path and non-numeric-path edge cases are handled naturally by the Number.isNaN guard. No control flow, queue behavior, or error-handling paths are altered.
No files require special attention.
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant Env as REDIS_URL env var participant URLParser as new URL() participant Decode as decodeURIComponent() participant BullOpts as redisQueueOptions participant Bull as Bull Queue (ioredis) Env->>URLParser: "redis://:pass%2Fword%3D@host:6379/15" URLParser-->>Decode: "url.password = "pass%2Fword%3D"" URLParser-->>Decode: "url.username = """ URLParser-->>BullOpts: "url.pathname = "/15"" Decode-->>BullOpts: "password = "pass/word="" Decode-->>BullOpts: "username = """ Note over BullOpts: db = parseInt("15", 10) = 15 BullOpts->>Bull: "{ host, port, password, username, db }" Bull-->>Bull: "AUTH pass/word= → OK, SELECT 15"%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant Env as REDIS_URL env var participant URLParser as new URL() participant Decode as decodeURIComponent() participant BullOpts as redisQueueOptions participant Bull as Bull Queue (ioredis) Env->>URLParser: "redis://:pass%2Fword%3D@host:6379/15" URLParser-->>Decode: "url.password = "pass%2Fword%3D"" URLParser-->>Decode: "url.username = """ URLParser-->>BullOpts: "url.pathname = "/15"" Decode-->>BullOpts: "password = "pass/word="" Decode-->>BullOpts: "username = """ Note over BullOpts: db = parseInt("15", 10) = 15 BullOpts->>Bull: "{ host, port, password, username, db }" Bull-->>Bull: "AUTH pass/word= → OK, SELECT 15"Reviews (2): Last reviewed commit: "Merge branch 'dev' into fix/redis-url-us..." | Re-trigger Greptile