Conversation
* enhance(frontend): アンテナ・リストの設定画面からタイムラインへの動線を追加 * Update Changelog * fix
* New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Korean) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Korean) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Romanian) * New translations ja-jp.yml (French) * New translations ja-jp.yml (Arabic) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Czech) * New translations ja-jp.yml (German) * New translations ja-jp.yml (Greek) * New translations ja-jp.yml (Dutch) * New translations ja-jp.yml (Norwegian) * New translations ja-jp.yml (Polish) * New translations ja-jp.yml (Portuguese) * New translations ja-jp.yml (Russian) * New translations ja-jp.yml (Slovak) * New translations ja-jp.yml (Swedish) * New translations ja-jp.yml (Turkish) * New translations ja-jp.yml (Ukrainian) * New translations ja-jp.yml (English) * New translations ja-jp.yml (Vietnamese) * New translations ja-jp.yml (Indonesian) * New translations ja-jp.yml (Bengali) * New translations ja-jp.yml (Thai) * New translations ja-jp.yml (Uzbek) * New translations ja-jp.yml (Lao) * New translations ja-jp.yml (Kabyle) * New translations ja-jp.yml (Japanese, Kansai) * New translations ja-jp.yml (Korean (Gyeongsang)) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Korean) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Romanian) * New translations ja-jp.yml (French) * New translations ja-jp.yml (Arabic) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Czech) * New translations ja-jp.yml (German) * New translations ja-jp.yml (Dutch) * New translations ja-jp.yml (Norwegian) * New translations ja-jp.yml (Polish) * New translations ja-jp.yml (Portuguese) * New translations ja-jp.yml (Russian) * New translations ja-jp.yml (Slovak) * New translations ja-jp.yml (Swedish) * New translations ja-jp.yml (Turkish) * New translations ja-jp.yml (Ukrainian) * New translations ja-jp.yml (English) * New translations ja-jp.yml (Vietnamese) * New translations ja-jp.yml (Indonesian) * New translations ja-jp.yml (Bengali) * New translations ja-jp.yml (Thai) * New translations ja-jp.yml (Uzbek) * New translations ja-jp.yml (Lao) * New translations ja-jp.yml (Japanese, Kansai) * New translations ja-jp.yml (Korean) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (English) * New translations ja-jp.yml (Japanese, Kansai) * New translations ja-jp.yml (Japanese, Kansai) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (English)
…16784) * fix: 並列deleteによるpostgresqlのdeadlock * chore: update comment
* chore: channelのdescriptionを空欄にできるようにする * update: CHANGELOG.md * update: CHANGELOG.md * fix: CHANGELOG.md * fix: CHANGELOG.md
* fix(frontend): 投稿フォームのアバター画像が縮むのを修正 * fix CHANGELOG.md * fix
* fix(frontend): カスタム絵文字(β)画面で変更行が正しくハイライトされない問題を修正 * rollback * 詳細度->!important
* fix(ci): DockleのCIが落ちるのを修正 * fix * fix * fix * fix * fix * downgrade dockle * fix
* New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Korean) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Russian) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Indonesian) * New translations ja-jp.yml (Indonesian) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Italian)
…sskey-dev#16793) * fix(devcontainer): devcontainerのバージョンアップ+Renovateでbump出来るように設定を追加 * Update renovate.json5 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update renovate.json5 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix matchFileNames * using trixie --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
* enhance(frontend): リアクションの受け入れ設定にキャプションを追加 * Update Changelog * CHANGELOG.mdを修正 * CHANGELOG.mdのコンフリクトを解消し、再度変更内容を記載 * 条件分岐をswitch文に変更 * chore: trigger CI re-run
* fix(frontend): ページのコンテンツがはみ出る問題を修正 * Update Changelog
* fix: ヘッダーメニューのチャンネルからチャンネルを新規作成の遷移先修正 * add changelog --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
|
@coderabbitai fullreview |
|
🧠 Learnings used✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 6
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
packages/backend/src/core/MfmService.ts (1)
105-112:⚠️ Potential issue | 🟠 MajorURLパースで例外が発生する可能性があります
new URL(href)は相対URLや不正なURLの場合にTypeErrorをスローします。toHtmlメソッド内では同様のURL処理がtry/catchで囲まれていますが(lines 375-380, 389-394, 417-422)、ここでは例外処理がありません。相対パス(例:
/user/foo)を含むHTMLを処理する際に、fromHtml全体が失敗する可能性があります。🔧 例外処理を追加する修正案
if (part.length === 2 && href) { //#region ホスト名部分が省略されているので復元する - const acct = `${txt}@${(new URL(href)).hostname}`; - text += acct; + try { + const acct = `${txt}@${(new URL(href)).hostname}`; + text += acct; + } catch { + text += txt; + } //#endregion🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/MfmService.ts` around lines 105 - 112, In toHtml (packages/backend/src/core/MfmService.ts) the expression new URL(href) can throw for relative or invalid URLs; wrap the hostname-reconstruction branch (the if block where part.length === 2 and href is used to build acct and append to text) in a try/catch and fall back to a safe behavior (e.g., use a provided base URL from the parser/context if available, or skip adding hostname and just append txt) so that malformed/relative href values do not bubble up and break fromHtml; ensure the catch logs or silently handles the error and preserves existing flow for part.length === 3.packages/backend/src/core/FetchInstanceMetadataService.ts (1)
249-312:⚠️ Potential issue | 🟡 MinorgetDescriptionメソッドにコピペエラーがあります。
getThemeColor、getSiteName、getDescriptionメソッドで使用されているquerySelector()とgetAttribute()はnode-html-parserで正式にサポートされており、型シグネチャも正しいです。ただし、
getDescriptionメソッドのmanifestフォールバック部分に問題があります。現在のコードは以下のようになっています:if (manifest) { return manifest.name ?? manifest.short_name; }これは
getSiteNameからのコピペエラーです。getDescriptionではmanifest.descriptionを返すべきです:if (manifest) { return manifest.description ?? manifest.short_name; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/FetchInstanceMetadataService.ts` around lines 249 - 312, The getDescription method has a copy-paste bug: its manifest fallback returns manifest.name (from getSiteName) instead of the description; in the getDescription function update the manifest branch to return manifest.description ?? manifest.short_name (replace the existing manifest.name ?? manifest.short_name) so the description fallback uses the correct manifest field..github/workflows/lint.yml (1)
80-91:⚠️ Potential issue | 🟠 Major
lintジョブのアクションバージョンが他のジョブと不整合です
pnpm_installおよびtypecheckジョブではactions/checkout@v4.3.0、pnpm/action-setup@v4.2.0、actions/setup-node@v4.4.0を使用していますが、lintジョブでは@v6を指定しています。actions/checkoutとactions/setup-nodeの現行メジャーバージョンはv4であり、v6は存在しない可能性があります。,
修正案
steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v4.3.0 with: fetch-depth: 0 submodules: true - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v4.2.0 with: run_install: false - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v4.4.0 with: node-version-file: '.node-version' cache: 'pnpm'What is the latest version of actions/checkout GitHub Action?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/lint.yml around lines 80 - 91, The lint job uses inconsistent and likely invalid action versions (`@v6`) for actions/checkout, pnpm/action-setup, and actions/setup-node; update the lint job to use the same stable versions as other jobs (actions/checkout@v4.3.0, pnpm/action-setup@v4.2.0, actions/setup-node@v4.4.0) and confirm the actions/checkout version is the repository's desired latest release before committing; adjust the three action references in the lint job block accordingly.packages/backend/src/core/FanoutTimelineEndpointService.ts (1)
120-145:⚠️ Potential issue | 🟠 Major新しいフィルタが DB fallback 経路に適用されていません。
ミュート済みチャンネルと停止済みユーザーの除外は Redis 側の
filterにしか入っていませんが、足りない分は Line 210 のdbFallback()結果をそのまま返しています。Redis ミスや部分読み込み時に、せっかく追加した除外条件をすり抜けたノートが混ざるので、この経路にも同じ条件を適用するか、dbFallback契約側に取り込む必要があります。Also applies to: 199-211
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/FanoutTimelineEndpointService.ts` around lines 120 - 145, The new mute/block/channel filters applied to the in-memory Redis path (built into the local variable filter in FanoutTimelineEndpointService) are not being applied to the dbFallback() path; update the code so that when ps.me is present you either (A) pass the same filter into the dbFallback call (extend dbFallback's signature to accept a predicate) or (B) post-filter the dbFallback() results with the same predicate before returning them; use the same checks (isUserRelated with userIdsWhoBlockingMe/userIdsWhoMeMuting and ps.ignoreAuthorFromBlock/ps.ignoreAuthorFromMute, renote checks including userIdsWhoMeMutingRenotes, isInstanceMuted with userMutedInstances, and isChannelRelated with userMutedChannels/ps.ignoreAuthorChannelFromMute) so both paths enforce identical exclusions.
♻️ Duplicate comments (6)
idea/MkAnimatedBg.dotted-ripples.vue (1)
192-195:⚠️ Potential issue | 🟠 Major既出ですが、uniform location の探索がまだレンダーループ内に残っています。
getUniformLocation()を毎フレーム 32 回呼ぶホットパスのままです。初期化時に配列へキャッシュして、ここではuniform2f/uniform1fだけを呼ぶ形にしてください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@idea/MkAnimatedBg.dotted-ripples.vue` around lines 192 - 195, The render loop is still calling gl.getUniformLocation() for each ripple every frame (hot path); move the location lookups into initialization and cache them (an array of WebGLUniformLocation for u_ripplePositions and u_rippleRadiuses keyed by index) so that inside the loop you only call gl.uniform2f/uniform1f with the cached locations and ripple values (use the existing shaderProgram, ripples[], and the names u_ripplePositions/u_rippleRadiuses to locate where to change).packages/backend/migration/1736686850345-createNoteDraft.js (2)
57-75:⚠️ Potential issue | 🔴 Critical
ON DELETE CASCADEがドラフトの意図しない削除を引き起こします(既出)
replyId、renoteId、channelIdの外部キーにON DELETE CASCADEが設定されていますが、NoteDraft.tsエンティティではcreateForeignKeyConstraints: falseと「参照先が削除されてもnullになる可能性がある」というコメントがあり、orphaned referenceを許容する設計意図が示されています。ON DELETE SET NULLに変更すべきです。,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1736686850345-createNoteDraft.js` around lines 57 - 75, Change the three foreign-key constraints for replyId, renoteId, and channelId to use ON DELETE SET NULL instead of ON DELETE CASCADE to match the NoteDraft entity's createForeignKeyConstraints: false/orphanable design; specifically update the SQL in the migration queries that add constraints "FK_NOTE_DRAFT_REPLY_ID", "FK_NOTE_DRAFT_RENOTE_ID", and "FK_NOTE_DRAFT_CHANNEL_ID" to reference "note"("id") or "channel"("id") with ON DELETE SET NULL so deleted parents become NULL rather than removing drafts.
18-27:⚠️ Potential issue | 🔴 Critical
NOT NULL制約が不足しています(既出)
localOnly、fileIds、visibleUserIds、hasPoll、pollChoicesカラムにNOT NULL制約がありません。TypeORMエンティティ定義では非nullが前提となっており、整合性を保つために制約を追加すべきです。,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1736686850345-createNoteDraft.js` around lines 18 - 27, In the CreateNoteDraft migration add NOT NULL constraints for the columns listed: change "localOnly" to boolean NOT NULL DEFAULT false, "fileIds" and "visibleUserIds" to varchar[] NOT NULL DEFAULT '{}', "hasPoll" to boolean NOT NULL DEFAULT false, and "pollChoices" to varchar(256)[] NOT NULL DEFAULT '{}'; update the SQL column definitions in the migration (the CREATE TABLE / column definitions containing "localOnly", "fileIds", "visibleUserIds", "hasPoll", "pollChoices") so they match the non-null expectations from the TypeORM entity.packages/backend/jest.js (1)
25-30:⚠️ Potential issue | 🔴 Criticalシグナル終了時の終了コード計算が不正です(既出)
Node.jsの
'exit'イベントで渡されるsignalは文字列(例:'SIGTERM')であり、128 + signalは"128SIGTERM"という不正な値になります。,
修正案
+import os from 'node:os'; + child.on('exit', (code, signal) => { if (code === null) { - process.exit(128 + signal); + const signalCode = signal ? os.constants.signals[signal] : undefined; + process.exit(signalCode != null ? 128 + signalCode : 1); } else { process.exit(code); } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/jest.js` around lines 25 - 30, The exit handler for child.on('exit', (code, signal) => ...) treats signal as a number and does process.exit(128 + signal), but signal is a string (e.g. "SIGTERM"); update the handler to detect when code is null and signal is non-null and compute a numeric exit code by resolving the signal name to its numeric value (e.g. use os.constants.signals[signal] or similar lookup) then call process.exit(128 + numericSignal); keep the existing behavior of process.exit(code) when code is a number and handle missing/unknown signal names with a sensible fallback (e.g. just exit(128) or exit(1))..github/workflows/test-backend.yml (1)
26-32:⚠️ Potential issue | 🟡 Minor
force_ffmpeg_cache_updateがまだワークフロー挙動に反映されていません。input を追加していますが、FFmpeg の restore/save ステップの条件にも key にも参照がないので、手動実行で
trueにしてもキャッシュ更新を強制できません。restore をスキップするだけでは既存 key を上書きできないため、save 側の key も input で分岐させる必要があります。Also applies to: 73-90
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/test-backend.yml around lines 26 - 32, The workflow input force_ffmpeg_cache_update is not used in the FFmpeg cache restore/save steps; update the FFmpeg restore and save steps so their if conditions reference github.event.inputs.force_ffmpeg_cache_update (skip restore when true) and modify the cache key generation (e.g., append a conditional suffix or use a different key when force_ffmpeg_cache_update is true) so a manual true run produces a different key and forces cache save; apply the same changes to both the restore step and the save step referenced in the FFmpeg-related steps.packages/backend/migration/1752502434151-no-action-on-draft-relation.js (1)
9-15:⚠️ Potential issue | 🔴 Critical
replyId/renoteId/channelIdの FK が up() で復元されていません。
up()は 4 本の制約を落としてuserIdだけ張り直しているので、適用後はreplyId/renoteId/channelIdが参照整合性なしになります。down()で 4 本とも復元していることから見ても、ここは意図した最終状態とずれています。🛠️ 修正例
async up(queryRunner) { await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_CHANNEL_ID"`); await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_USER_ID"`); await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_RENOTE_ID"`); await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_REPLY_ID"`); await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_e4983f28b4b18b03491536052f5" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_REPLY_ID" FOREIGN KEY ("replyId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_RENOTE_ID" FOREIGN KEY ("renoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_CHANNEL_ID" FOREIGN KEY ("channelId") REFERENCES "channel"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); }Also applies to: 17-22
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1752502434151-no-action-on-draft-relation.js` around lines 9 - 15, The up() migration only re-adds the userId FK but fails to restore the three other foreign keys dropped earlier (FK_NOTE_DRAFT_CHANNEL_ID, FK_NOTE_DRAFT_RENOTE_ID, FK_NOTE_DRAFT_REPLY_ID), leaving replyId/renoteId/channelId without referential integrity; update the up() implementation to add back all four constraints (same constraint identifiers: FK_NOTE_DRAFT_CHANNEL_ID, FK_NOTE_DRAFT_RENOTE_ID, FK_NOTE_DRAFT_REPLY_ID, and the existing FK_e4983f28b4b18b03491536052f5 for userId) using queryRunner.query(...) so each FOREIGN KEY ("channelId"), FOREIGN KEY ("renoteId"), and FOREIGN KEY ("replyId") reference their correct target tables with the appropriate ON DELETE/ON UPDATE actions matching the down() definitions.
🟠 Major comments (30)
packages/backend/migration/1753868431598-remove_note_constraints.js-15-16 (1)
15-16:⚠️ Potential issue | 🟠 Majorロールバック時に孤立参照があると
down()が失敗します。
up()実行後はreplyId/renoteIdの孤立データを作れてしまうので、Line 15 と Line 16 のADD CONSTRAINTはロールバック時にその場で失敗します。down()側で先に不正参照をNULLに戻すか削除してから制約を張り直したほうが安全です。修正案
async down(queryRunner) { + await queryRunner.query(` + UPDATE "note" AS n + SET "replyId" = NULL + WHERE "replyId" IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM "note" AS parent WHERE parent."id" = n."replyId" + ) + `); + await queryRunner.query(` + UPDATE "note" AS n + SET "renoteId" = NULL + WHERE "renoteId" IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM "note" AS parent WHERE parent."id" = n."renoteId" + ) + `); await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_17cb3553c700a4985dff5a30ff5" FOREIGN KEY ("replyId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_52ccc804d7c69037d558bac4c96" FOREIGN KEY ("renoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1753868431598-remove_note_constraints.js` around lines 15 - 16, down() currently re-adds the foreign keys FK_17cb3553c700a4985dff5a30ff5 and FK_52ccc804d7c69037d558bac4c96 for columns replyId and renoteId without first handling orphaned rows, which causes failures on rollback; update the migration's down() to first null out or delete rows where replyId or renoteId reference missing note ids (e.g., run a DELETE or UPDATE setting replyId/renoteId = NULL for any rows whose referenced note id does not exist), then re-add the ALTER TABLE ... ADD CONSTRAINT for those same constraint names so the constraint creation cannot fail due to existing orphaned references.locales/ko-GS.yml-654-656 (1)
654-656:⚠️ Potential issue | 🟠 Major新規セクションの翻訳が不完全です。基準ロケールとの差分を埋めてください。
以下のキーが
ko-GS.ymlから漏れており、ユーザーには基準言語がフォールバック表示されます。
_imageEditing._vars: 17 個のキー不足(camera_model、camera_f、camera_iso、year、month、day、hour、minute、second、caption、filename_without_ext、gps_lat、gps_long、camera_mm、camera_mm_35、camera_lens_model、camera_s)_watermarkEditor: 29 個のキー不足(title、text、angle、scale、opacity、position、type、stripe、stripeWidth、stripeFrequency、polkadot、polkadotMainDotRadius、polkadotMainDotOpacity、polkadotSubDotRadius、polkadotSubDotOpacity、polkadotSubDotDivisions、repeat、margin、qr、checker、cover、preserveBoundingRect、leaveBlankToAccountUrl、advanced、driveFileTypeWarn、driveFileTypeWarnDescription、failedToLoadImage、quitWithoutSaveConfirm、tip)_qr: 13 個のキー不足(startQr、stopQr、readTabTitle、chooseCamera、mfm、raw、scanFile、noQrCodeFound、cannotToggleFlash、turnOnFlash、turnOffFlash、shareTitle、shareText)すべてのキーを翻訳して、各セクションが完全になるように修正してください。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ko-GS.yml` around lines 654 - 656, The ko-GS.yml translations are incomplete for the new sections; add the missing keys under _imageEditing._vars (camera_model, camera_f, camera_iso, year, month, day, hour, minute, second, caption, filename_without_ext, gps_lat, gps_long, camera_mm, camera_mm_35, camera_lens_model, camera_s), the entire _watermarkEditor block (title, text, angle, scale, opacity, position, type, stripe, stripeWidth, stripeFrequency, polkadot, polkadotMainDotRadius, polkadotMainDotOpacity, polkadotSubDotRadius, polkadotSubDotOpacity, polkadotSubDotDivisions, repeat, margin, qr, checker, cover, preserveBoundingRect, leaveBlankToAccountUrl, advanced, driveFileTypeWarn, driveFileTypeWarnDescription, failedToLoadImage, quitWithoutSaveConfirm, tip) and the _qr block (startQr, stopQr, readTabTitle, chooseCamera, mfm, raw, scanFile, noQrCodeFound, cannotToggleFlash, turnOnFlash, turnOffFlash, shareTitle, shareText) with Korean translations matching the base locale keys and structure so no fallback occurs; locate and update the _imageEditing._vars, _watermarkEditor, and _qr sections in ko-GS.yml to mirror the base locale keys and provide appropriate Korean strings for each missing key.packages/backend/src/core/DriveService.ts-548-565 (1)
548-565:⚠️ Potential issue | 🟠 Majorリモート経由だと容量制限をすり抜けます。
ここは
maxFileSize超過をローカルユーザーにしか拒否していないので、リモートユーザーは上限超過ファイルをそのまま保存できます。さらに単体でdriveCapacityを超えるファイルでもexpireOldFile(driveCapacity - info.size)に進めてしまうため、既存ファイルを消しても収まらないケースを防げません。加えてexpireOldFile()側は各deleteFile()を await していないので、このawaitでも空き容量確保は保証されません。少なくとも単体サイズ超過は即 reject し、容量超過時は削除完了を待てる形にしないと policy enforcement が崩れます。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/DriveService.ts` around lines 548 - 565, The code currently only rejects oversized uploads for local users and calls expireOldFile incorrectly and without awaiting deletes; fix by (1) checking if info.size > maxFileSize OR info.size > driveCapacity and throwing the same IdentifiableError for all users (use the same or an appropriate error id) so remote users cannot bypass maxFileSize or single-file capacity; (2) compute requiredFree = (usage + info.size) - driveCapacity and, if requiredFree > 0, call await this.expireOldFile(userRecord, requiredFree) where userRecord comes from await this.usersRepository.findOneByOrFail({ id: user.id }); and (3) update expireOldFile/deleteFile so it awaits each delete (ensure expireOldFile returns a Promise that resolves after all deleteFile awaits complete) so the await here guarantees space was freed before proceeding. Include references to maxFileSize, isLocalUser, driveCapacity, driveFileEntityService.calcDriveUsageOf, expireOldFile, usersRepository.findOneByOrFail, and deleteFile when making changes.locales/id-ID.yml-403-403 (1)
403-403:⚠️ Potential issue | 🟠 Major
enableMcaptchaが空文字ですLine 403 が空だと設定トグルのラベルが空表示になり、何を有効化する項目なのか分からなくなります。設定画面の操作性を直接壊すので、ここは埋めるべきです。
修正案
-enableMcaptcha: "" +enableMcaptcha: "Nyalakan mCaptcha"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/id-ID.yml` at line 403, The locale key enableMcaptcha currently has an empty value; replace it with a meaningful Indonesian label (for example "Aktifkan mCaptcha" or similar) so the settings toggle displays correctly; update the string for the enableMcaptcha key in locales/id-ID.yml to a concise, user-facing phrase in Indonesian.packages/backend/src/core/FileInfoService.ts-281-288 (1)
281-288:⚠️ Potential issue | 🟠 Major
sharp.resize(299, 299)のデフォルト動作で画像が切り抜かれます。Line 282 は
fitオプションが未指定のため、Sharp のデフォルト値coverが適用されます。fit: "cover"では指定された両方の寸法に対して、アスペクト比を保ちながら画像をクロップします。加えてwithoutEnlargement: falseにより小さい画像も拡大されるため、元画像から大きく乖離した入力が AI 判定に渡される可能性があります。アスペクト比を保ちながら全体を収めるリサイズに変更することを推奨します。修正案
const png = await (await sharpBmp(source, mime)) .resize(299, 299, { - withoutEnlargement: false, + fit: 'inside', + withoutEnlargement: true, }) .rotate() .flatten({ background: { r: 119, g: 119, b: 119 } }) // 透過部分を18%グレーで塗りつぶす .png()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/FileInfoService.ts` around lines 281 - 288, The resize call in FileInfoService.ts uses sharp (via sharpBmp) without a fit option so Sharp defaults to fit: "cover" and crops the image; also withoutEnlargement: false allows upscaling. Update the pipeline that produces the png buffer (the chain starting at sharpBmp(source, mime) and assigned to png) to specify fit: "contain" (so the whole image is preserved with aspect ratio) and set withoutEnlargement: true (to avoid upscaling small images); keep the existing rotate() and flatten() behavior so output remains the same otherwise.packages/backend/migration/1752509043847-migration-cleanup.js-24-25 (1)
24-25:⚠️ Potential issue | 🟠 Major
SET NOT NULLの前に既存 NULL を埋めてください。既存行に NULL が 1 件でもあると、この migration はここで停止します。Line 23 のコメントどおり
flash.visibilityは過去 migration で制約漏れがあり、note_draft側も hand-written SQL の補正なので、SET NOT NULLの前にUPDATE ... WHERE ... IS NULLで安全な値へ backfill して self-contained にしておいた方が安全です。Also applies to: 40-45
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1752509043847-migration-cleanup.js` around lines 24 - 25, Before the ALTER TABLE that sets flash.visibility NOT NULL, add a backfill step that updates any existing NULLs to a safe default so the migration won't fail; specifically, before queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" SET NOT NULL`), run an UPDATE on the "flash" table (e.g., via queryRunner.query with an UPDATE ... WHERE "visibility" IS NULL) to set a valid non-null value, and do the same for the analogous hand-written SQL block that touches note_draft (lines around the second ALTER in the file) so both flash.visibility and the note_draft column are backfilled prior to their respective ALTER COLUMN ... SET NOT NULL statements.packages/backend/migration/1752509043847-migration-cleanup.js-55-56 (1)
55-56:⚠️ Potential issue | 🟠 MajorLine 56:
userIdコメントの rollback 値が誤っています。元の migration では
userIdにコメント定義がないため、down()ではIS NULLに戻す必要があります。他の 3 列(replyId、renoteId、channelId)は正しくIS NULLに設定されていますが、Line 56 のuserIdだけが'The ID of author.'のままで完全な rollback になっていません。Line 56 を以下に修正してください:
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."userId" IS NULL`);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1752509043847-migration-cleanup.js` around lines 55 - 56, The rollback for the "userId" column is incorrect: update the down() migration so the COMMENT ON COLUMN "note_draft"."userId" is reverted to IS NULL instead of keeping 'The ID of author.'; locate the queryRunner.query call that handles "note_draft"."userId" in the migration (the line that currently sets the comment to 'The ID of author.') and change it to set the comment to NULL to match the original state and the other columns (replyId, renoteId, channelId).locales/kn-IN.yml-47-48 (1)
47-48:⚠️ Potential issue | 🟠 MajorKannadaの「Mention」は名詞形の訳語が必要
現在の
ಹೆಸರಿಸಿದは過去分詞形で、UI ラベルとして使用する名詞としては不自然です。メンション一覧・通知種別・テーマ設定の複数箇所で影響するため、より自然な名詞形のಉಲ್ಲೇಖ(単数) /ಉಲ್ಲೇಖಗಳು(複数) への変更を推奨します。適用行: 47-48, 69-71, 87
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/kn-IN.yml` around lines 47 - 48, The current Kannada translations for the UI keys mention and mentions use the past-participial form "ಹೆಸರಿಸಿದ"; replace the values for the keys mention -> "ಉಲ್ಲೇಖ" and mentions -> "ಉಲ್ಲೇಖಗಳು" to use the correct noun forms, and update any other occurrences of these keys in the same locale file (the other mention/mentions entries present) so all UI labels (mention list, notification types, theme settings) consistently use ಉಲ್ಲೇಖ / ಉಲ್ಲೇಖಗಳು.packages/backend/scripts/dev.mjs-52-61 (1)
52-61:⚠️ Potential issue | 🟠 Major非同期ハンドラを直列化しないと再起動フローが競合します。
on('message', async ...)は前のハンドラの完了を待たないため、ファイル保存連打で複数のkillProc() -> execBuildAssets() -> execStart()シーケンスが同時実行されます。これはプロセス状態を破壊する可能性があります。また、execBuildAssets()の失敗は未処理のままになります。修正例
+let restartChain = Promise.resolve(); + (async () => { execaNode( './node_modules/nodemon/bin/nodemon.js', [ '-w', 'src', '-e', 'ts,js,mjs,cjs,json,pug', '--exec', 'pnpm', 'run', 'build', ], { stdio: [process.stdin, process.stdout, process.stderr, 'ipc'], serialization: "json", }) - .on('message', async (message) => { - if (message.type === 'exit') { - // かならずbuild->build-assetsの順番で呼び出したいので、 - // 少々トリッキーだがnodemonからのexitイベントを利用してbuild-assets->startを行う。 - // pnpm restartをbuildが終わる前にbuild-assetsが動いてしまうので、バラバラに呼び出す必要がある - - await killProc(); - await execBuildAssets(); - execStart(); - } - }); + .on('message', (message) => { + if (message.type !== 'exit') return; + restartChain = restartChain + .catch(() => {}) + .then(async () => { + // かならずbuild->build-assetsの順番で呼び出したいので、 + // 少々トリッキーだがnodemonからのexitイベントを利用してbuild-assets->startを行う。 + // pnpm restartをbuildが終わる前にbuild-assetsが動いてしまうので、バラバラに呼び出す必要がある + await killProc(); + await execBuildAssets(); + execStart(); + }) + .catch((error) => { + console.error(error); + }); + }); })();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/scripts/dev.mjs` around lines 52 - 61, The message handler currently spawns concurrent restart sequences because on('message', async (message) => { ... }) doesn't serialize concurrent invocations; to fix, serialize the restart flow by adding a simple in-process lock/queue around the on('message') handler: when message.type === 'exit' check/acquire a boolean or Promise-based lock before calling killProc(), await killProc(), await execBuildAssets() inside a try/catch that logs and handles errors, then call execStart() only after successful build-assets and finally release the lock; reference symbols: the on('message'...) handler, killProc, execBuildAssets, execStart.packages/backend/scripts/dev.mjs-31-36 (1)
31-36:⚠️ Potential issue | 🟠 Majorバックエンドプロセスの既終了時に
killProc()が永遠にハングします。Line 35 で
kill()の後にexitリスナーを追加していますが、プロセスが既に終了済みの場合、exitイベントは再発火しません。Node.js の EventEmitter では、イベント発火後にリスナーを追加しても、そのイベントは再生されないためです。バックエンドが一度クラッシュすると、次の再起動シーケンスがここで永遠に停止します。修正例
async function killProc() { if (backendProcess) { - backendProcess.catch(() => {}); // backendProcess.kill()によって発生する例外を無視するためにcatch()を呼び出す - backendProcess.kill(); - await new Promise(resolve => backendProcess.on('exit', resolve)); - backendProcess = undefined; + const proc = backendProcess; + backendProcess = undefined; + proc.catch(() => {}); // kill に伴う reject を握りつぶす + if (proc.exitCode === null && proc.signalCode === null) { + const exited = new Promise(resolve => proc.once('exit', resolve)); + proc.kill(); + await exited; + } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/scripts/dev.mjs` around lines 31 - 36, killProc currently calls backendProcess.kill() then awaits backendProcess.on('exit', ...) which hangs if the process already exited; fix by ensuring you either attach the exit listener before calling kill or short-circuit when the child has already exited (check backendProcess.exitCode !== null or backendProcess.killed) so the Promise resolves immediately; update the killProc implementation (references: function killProc, backendProcess.kill(), backendProcess.on('exit', ...)) to attach the listener with once('exit') before calling kill or return immediately when exitCode is non-null.packages/backend/scripts/dev.mjs-19-29 (1)
19-29:⚠️ Potential issue | 🟠 Majorバックエンド起動失敗時の promise rejection がハンドリングされていません。
execaNode()が返す Promise は起動失敗時(ファイルが見つからない、権限エラーなど)に reject されます。しかしexecStart()ではこの reject を処理していないため、起動失敗時に unhandled rejection が発生し、Node.js プロセスがクラッシュします。Line 33 の.catch()はkill()時の例外処理のみで、startup 失敗には対応していません。修正例
function execStart() { // pnpm run start を呼び出したいが、windowsだとプロセスグループ単位でのkillが出来ずゾンビプロセス化するので // 上記と同等の動きをするコマンドで子・孫プロセスを作らないようにしたい - backendProcess = execaNode('./built/boot/entry.js', [], { + const proc = execaNode('./built/boot/entry.js', [], { stdout: process.stdout, stderr: process.stderr, env: { 'NODE_ENV': 'development', }, }); + backendProcess = proc; + void proc.catch((error) => { + if (proc.killed) return; + console.error(error); + }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/scripts/dev.mjs` around lines 19 - 29, The execStart function currently assigns backendProcess = execaNode(...) but does not handle rejections from execaNode; update execStart to handle startup failures by attaching a rejection handler (or awaiting in an async function) to the execaNode() Promise returned by backendProcess, log the error (including error.message) and perform graceful shutdown/exit (e.g. call kill() or process.exit(1)) so unhandled promise rejections don't crash the process; reference execStart, execaNode, backendProcess and existing kill() logic when adding the .catch or try/catch around the spawn.packages/backend/src/core/AiService.ts-74-75 (1)
74-75:⚠️ Potential issue | 🟠 Major
decodeImage()の直接呼び出しで GIF などの形式が壊れる可能性があります。Line 75 の
tf.node.decodeImage(buffer, 3)はexpandAnimationsパラメータを省略しており、これはデフォルト値のtrueに設定されます。その結果、GIF ファイルは 4D テンソル[frames, height, width, 3]を返しますが、model.classify()が期待する入力は 3D テンソル[height, width, 3]です。この不一致により、GIF ファイルをこの経路で処理する場合、実行時エラーが発生します。一方、mime が指定される経路(line 64-72)では sharp.js により適切に前処理され、常に 3D テンソルが返されます。
as anyによる型警告の抑制がこの潜在的な失敗を隠しています。GIF を含む全形式で一貫性を持たせるため、
expandAnimationsを明示的にfalseに設定し、アニメーション GIF を最初のフレームに固定する必要があります。修正案
- return await tf.node.decodeImage(buffer, 3) as any; + return await tf.node.decodeImage(buffer, 3, 'int32', false);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/AiService.ts` around lines 74 - 75, The tf.node.decodeImage call currently omits the expandAnimations flag which leaves GIFs expanded into 4D tensors and breaks model.classify; update the call to tf.node.decodeImage(buffer, 3, /*expandAnimations=*/false) so animated images are fixed to the first frame and produce a 3D tensor, remove or tighten the "as any" suppression if possible, and ensure the same preprocessing behavior as the mime-handled path so model.classify receives a consistent [height,width,3] tensor.packages/backend/migration/1755168347001-PageCountInNote.js-10-10 (1)
10-10:⚠️ Potential issue | 🟠 Major
pageCountはsmallintではなくintegerが安全です。
smallintは-32768~+32767の範囲に制限され、この範囲外の値を代入すると PostgreSQL はエラーを発生させます。pageCountに厳密な上限がないなら、参照元ページ数が多いノートで将来の更新や再集計が失敗し得ます。修正例
- await queryRunner.query(`ALTER TABLE "note" ADD "pageCount" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "note" ADD "pageCount" integer NOT NULL DEFAULT 0`);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1755168347001-PageCountInNote.js` at line 10, The migration uses smallint for the new column in the queryRunner.query call (ALTER TABLE "note" ADD "pageCount" smallint NOT NULL DEFAULT '0'), which risks overflow; change the column type to integer and set the default to 0 (e.g., ALTER TABLE "note" ADD "pageCount" integer NOT NULL DEFAULT 0) so future large page counts won't error — update the migration SQL inside the 1755168347001-PageCountInNote.js file where queryRunner.query is invoked to use integer instead of smallint and adjust the default literal accordingly.idea/MkAnimatedBg.dotted.vue-142-150 (1)
142-150:⚠️ Potential issue | 🟠 MajorChromatic 用の 1 フレーム描画が非決定になっています。
u_seedを分岐前で乱数初期化しているので、isChromatic()側でも毎回違うパターンになります。これだとスナップショットが安定しません。修正案
- gl.uniform1f(u_seed, Math.random() * 1000); + const chromatic = isChromatic(); + gl.uniform1f(u_seed, chromatic ? 0.0 : Math.random() * 1000); gl.uniform1f(u_scale, props.scale); gl.uniform1f(u_angle, 0.0); gl.uniform1f(u_radius, 0.15); gl.uniform3fv(u_color, [0.5, 1.0, 0]); - if (isChromatic()) { + if (chromatic) { gl.uniform1f(u_time, 0); gl.drawArrays(gl.TRIANGLES, 0, 6); } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@idea/MkAnimatedBg.dotted.vue` around lines 142 - 150, The chromatic branch is non-deterministic because u_seed is set to Math.random() before the isChromatic() check; change the code so that when isChromatic() is true you pass a deterministic seed (e.g., 0 or a fixed value) to gl.uniform1f(u_seed, ...) right before gl.drawArrays, and only use Math.random() for u_seed in the non-chromatic path; keep u_time, gl.drawArrays, and the isChromatic() check intact but ensure the seed assignment is moved or conditional so chromatic snapshots are stable.idea/MkAnimatedBg.dotted-ripples.vue-162-164 (1)
162-164:⚠️ Potential issue | 🟠 MajorChromatic では透明な 1 フレームしか出ません。
この分岐では
u_rippleRadiusesを一度も正の値にしていないので、uniform 初期値のままgetRipple()が常に 0 になります。今のままだとスナップショットが空です。修正案
if (isChromatic()) { + const chromaticRipplePosition = gl.getUniformLocation(shaderProgram, 'u_ripplePositions[0]'); + const chromaticRippleRadius = gl.getUniformLocation(shaderProgram, 'u_rippleRadiuses[0]'); + gl.uniform2f(chromaticRipplePosition, 0.0, 0.0); + gl.uniform1f(chromaticRippleRadius, 0.35); gl.uniform1f(u_time, 0); gl.drawArrays(gl.TRIANGLES, 0, 6); } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@idea/MkAnimatedBg.dotted-ripples.vue` around lines 162 - 164, Branch for isChromatic() currently draws with u_rippleRadiuses left at their default (zero), so getRipple() returns 0 and the snapshot is empty; update the isChromatic() path to initialize the ripple uniforms the same way as the non-chromatic path before drawing: set u_rippleRadiuses (and any related uniforms like u_time if needed) to positive/expected initial values used by getRipple(), or call the existing ripple-initialization routine (the code that prepares u_rippleRadiuses/getRipple) so gl.drawArrays renders visible ripples..devcontainer/devcontainer.json-7-8 (1)
7-8:⚠️ Potential issue | 🟠 Majordevcontainer の Node.js バージョンと最小バージョン要件の不整合を修正してください。
devcontainer で Node.js
24.10.0を指定していますが、.github/min.node-versionでは22.21.0が最小バージョンとして定義されています。開発環境と最小要件間のバージョン差異を統一してください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.devcontainer/devcontainer.json around lines 7 - 8, The devcontainer Node version under the JSON key "ghcr.io/devcontainers/features/node:1" is set to "24.10.0" but the project minimum in .github/min.node-version is "22.21.0"; pick one source of truth and make them consistent by either (A) lowering the "version" value for "ghcr.io/devcontainers/features/node:1" to "22.21.0" to match the min.node-version, or (B) bumping the string inside .github/min.node-version to "24.10.0" (and update any CI/tooling that enforces the min) so both entries match. Ensure the chosen numeric version string is valid and consistent across both symbols.packages/backend/assets/LICENSE_MisskeyHQ-13-21 (1)
13-21:⚠️ Potential issue | 🟠 Major利用許諾の条件が前後で矛盾しています。
Line 13 は「再配布・再利用不可」と読めますが、Line 21 は「use, copy, and publish ... for any purpose」を許可しています。このままだと利用者が何を許されているのか判断できず、配布時のコンプライアンス判断もぶれます。許可したい範囲を 1 つに絞って文言を統一してください。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/assets/LICENSE_MisskeyHQ` around lines 13 - 21, The license text contains contradictory clauses: the sentence "These files may not be redistributed, modified, or reused in any way without explicit permission from MisskeyHQ." conflicts with "Permission is hereby granted to the user of this digital asset and related documentation to use, copy, and publish the asset for any purpose, provided that this notice and disclaimer of warranty appears in all copies."; pick one definitive policy (either restrictive: disallow redistribution/modification, or permissive: allow use/copy/publish under conditions) and rewrite both the "These files may not be redistributed..." and "Permission is hereby granted..." passages to express that single policy consistently, updating related sentences such as "The user is expressly prohibited..." to match the chosen grant or prohibition so the license no longer contradicts itself.packages/backend/src/core/ChatService.ts-334-341 (1)
334-341:⚠️ Potential issue | 🟠 Major
readAllChatMessages()が未読状態を完全には消していません。ここで消しているのは
newChatMessagesExists:${readerId}だけなので、hasUnreadMessages()は false になりますが、getUserReadStateMap()/getRoomReadStateMap()が見ているnewUserChatMessageExists:*とnewRoomChatMessageExists:*は残ります。結果として「全件既読」にした直後でも一覧や詳細側の未読表示が食い違います。.github/workflows/test-production.yml-32-35 (1)
32-35:⚠️ Potential issue | 🟠 Major最小サポート Node.js の検証が外れています。
.node-versionファイルが主要バージョン22のみを指定しているのに対し、実際の最小要件はpackage.jsonで^22.21.0 || ^24.0.0、.github/min.node-versionで22.21.0です。このため、本ワークフローは Node.js の最新 22.x で動作確認されますが、最小サポート版22.21.0での動作は保証されません。
.node-versionを最小サポート版で固定するか、すべてのバージョン定義(.node-version、.github/min.node-version、package.jsonのengines.node)の整合性を常に保つ仕組みが必要です。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/test-production.yml around lines 32 - 35, The workflow is using .node-version which only pins major 22, so it may not run on the minimum supported Node.js (22.21.0); update the workflow in test-production.yml to reference the canonical minimum Node version source (either set node-version-file to '.github/min.node-version' or hard-pin node-version to '22.21.0'), and ensure this aligns with the .node-version file, .github/min.node-version, and package.json engines.node so all three definitions remain consistent..config/example.yml-108-116 (1)
108-116:⚠️ Potential issue | 🟠 Major
trustProxyの既定値が全プロキシ信頼になっており、IP ベースのセキュリティ制御が脆弱化しています。
packages/backend/src/server/ServerService.ts行 82 でtrustProxy: this.config.trustProxy ?? trueと設定されており、設定がない場合は無条件に全プロキシを信頼します。コード全体でrequest.ipがレート制限(SigninApiService行 93、SigninWithPasskeyApiService行 89 など)とユーザーアカウントのアクセス履歴ロギング(ApiCallService行 402-404)に使用されているため、バックエンドが逆プロキシを経由せずに直接到達可能な場合、X-Forwarded-Forヘッダーでクライアントが IP スプーフィングを行えます。.config/example.yml行 113 の文書でこの危険な既定を「Default: trust all proxies」と記載すると、安全でない設定が正常のように見えてしまいます。ドキュメントの推奨値を
trustProxy: 1(ホップ数指定)に変更し、直接アクセス不可の構成を強く明記してください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.config/example.yml around lines 108 - 116, Update the `.config/example.yml` documentation to stop suggesting "Default: trust all proxies" and instead set the recommended example value to "trustProxy: 1" with a clear, strong warning that trusting all proxies is unsafe because code paths use request.ip (see ServerService.ts where trustProxy is set via this.config.trustProxy ?? true, and usages in SigninApiService, SigninWithPasskeyApiService, and ApiCallService), and explicitly call out that production deployments should only trust known proxy hops or configure the server to be unreachable directly from the internet to prevent X-Forwarded-For IP spoofing.packages/backend/eslint.config.js-27-28 (1)
27-28:⚠️ Potential issue | 🟠 Major
no-unused-varsの全域無効化により未使用変数の検知が完全に消えています。Line 28 で backend 全体の TS/TSX ファイルに対して
@typescript-eslint/no-unused-varsを無効化していますが、TypeScript コンパイラオプション(tsconfig.json)ではnoUnusedLocalsとnoUnusedParametersが両方ともfalseに設定されています。そのため、ESLint ルールが無効な状態では未使用変数・引数の検知が完全に失われます。問題のあるファイルに限定して override を設定するか、TypeScript コンパイラオプションで有効化してください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/eslint.config.js` around lines 27 - 28, 現在 backend の eslint.config.js で '@typescript-eslint/no-unused-vars' を全域オフにしているため未使用変数検知が無効化されています。修正案としては(1)eslint.config.js 内の rules から全域オフを取り除くか、該当ルールを特定のファイル群だけに限定する override を追加して全体の抑制をやめる、または(2)TypeScript 側で tsconfig.json の noUnusedLocals と noUnusedParameters を true に変更してコンパイラレベルで未使用検知を有効にする、どちらかを選んで適用してください。対象となる識別子は '@typescript-eslint/no-unused-vars'(eslint.config.js)と 'noUnusedLocals'/'noUnusedParameters'(tsconfig.json)です。packages/backend/migration/1745378064470-composite-note-index.js-15-25 (1)
15-25:⚠️ Potential issue | 🟠 Majorconcurrent モードでも
DROP INDEXが通常ロックのままです。
CREATE INDEX CONCURRENTLYに合わせてtransaction = falseにしている一方、18行目のDROP INDEX IF EXISTS(concurrent ブロック内)、25行目のDROP INDEX IF EXISTS(常に実行)、および 33行目のdown()メソッド内のDROP INDEX IF EXISTSはいずれも通常の DROP のままです。これだと大きい"note"テーブルで書き込みをブロックするため、フラグを有効にしても非ブロッキング化の目的を満たせません。concurrent モードではDROP INDEX CONCURRENTLY IF EXISTS ...を使い分けた方が安全です。修正例
async up(queryRunner) { const concurrently = isConcurrentIndexMigrationEnabled(); + const dropIndex = concurrently + ? 'DROP INDEX CONCURRENTLY IF EXISTS' + : 'DROP INDEX IF EXISTS'; if (concurrently) { const hasValidIndex = await queryRunner.query(`SELECT indisvalid FROM pg_index INNER JOIN pg_class ON pg_index.indexrelid = pg_class.oid WHERE pg_class.relname = 'IDX_724b311e6f883751f261ebe378'`); if (hasValidIndex.length === 0 || hasValidIndex[0].indisvalid !== true) { - await queryRunner.query(`DROP INDEX IF EXISTS "IDX_724b311e6f883751f261ebe378"`); + await queryRunner.query(`${dropIndex} "IDX_724b311e6f883751f261ebe378"`); await queryRunner.query(`CREATE INDEX CONCURRENTLY "IDX_724b311e6f883751f261ebe378" ON "note" ("userId", "id" DESC)`); } } else { await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_724b311e6f883751f261ebe378" ON "note" ("userId", "id" DESC)`); } - await queryRunner.query(`DROP INDEX IF EXISTS "IDX_5b87d9d19127bd5d92026017a7"`); + await queryRunner.query(`${dropIndex} "IDX_5b87d9d19127bd5d92026017a7"`); await queryRunner.query(`ANALYZE "user", "note"`); } async down(queryRunner) { - const mayConcurrently = isConcurrentIndexMigrationEnabled() ? 'CONCURRENTLY' : ''; - await queryRunner.query(`DROP INDEX IF EXISTS "IDX_724b311e6f883751f261ebe378"`); - await queryRunner.query(`CREATE INDEX ${mayConcurrently} "IDX_5b87d9d19127bd5d92026017a7" ON "note" ("userId")`); + const concurrently = isConcurrentIndexMigrationEnabled(); + const createConcurrently = concurrently ? 'CONCURRENTLY ' : ''; + const dropIndex = concurrently + ? 'DROP INDEX CONCURRENTLY IF EXISTS' + : 'DROP INDEX IF EXISTS'; + + await queryRunner.query(`${dropIndex} "IDX_724b311e6f883751f261ebe378"`); + await queryRunner.query(`CREATE INDEX ${createConcurrently}"IDX_5b87d9d19127bd5d92026017a7" ON "note" ("userId")`); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1745378064470-composite-note-index.js` around lines 15 - 25, The DROP INDEX statements currently use the standard blocking form; update them to use the concurrent-safe form when running in concurrent mode: inside the concurrently branch change the DROP INDEX IF EXISTS at the queryRunner.query call (the one checked by hasValidIndex) to use DROP INDEX CONCURRENTLY IF EXISTS, change the unconditional DROP INDEX IF EXISTS after the branch to use DROP INDEX CONCURRENTLY IF EXISTS when the concurrently flag is true, and make the corresponding DROP INDEX IF EXISTS in the down() method use DROP INDEX CONCURRENTLY IF EXISTS when transaction=false/concurrently is enabled; locate these calls by searching for queryRunner.query invocations that drop indices including the index names "IDX_724b311e6f883751f261ebe378" and "IDX_5b87d9d19127bd5d92026017a7".packages/backend/src/config.ts-311-316 (1)
311-316:⚠️ Potential issue | 🟠 Majormanifest の存在だけでなくエントリキー欠落も吸収してください。
manifest.json自体が存在していてもsrc/_boot_.ts/src/boot.tsのキーが無い場合、ここはundefinedを返します。今回frontendEntry/frontendEmbedEntryを{ file: string | null }に変更しているので、部分ビルドやエントリ名変更時に型と実値がずれて.fileアクセスで落ちます。💡 修正案
const frontendManifest = frontendManifestExists ? JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_vite_/manifest.json`, 'utf-8')) - : { 'src/_boot_.ts': { file: null } }; + : {}; const frontendEmbedManifest = frontendEmbedManifestExists ? JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8')) - : { 'src/boot.ts': { file: null } }; + : {}; @@ - frontendEntry: frontendManifest['src/_boot_.ts'], + frontendEntry: frontendManifest['src/_boot_.ts'] ?? { file: null }, frontendManifestExists: frontendManifestExists, - frontendEmbedEntry: frontendEmbedManifest['src/boot.ts'], + frontendEmbedEntry: frontendEmbedManifest['src/boot.ts'] ?? { file: null }, frontendEmbedManifestExists: frontendEmbedManifestExists,Also applies to: 406-409
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/config.ts` around lines 311 - 316, The manifest parsing currently assumes keys 'src/_boot_.ts' and 'src/boot.ts' exist; update the logic that builds frontendManifest/frontendEmbedManifest (and the similar code around the frontendEntry/frontendEmbedEntry usage) to defensively check for the entry key and fall back to { file: null } if the key is missing or its value is falsy: after JSON.parse(...) verify parsedManifest['src/_boot_.ts'] (and parsedEmbedManifest['src/boot.ts']) and assign frontendEntry/frontendEmbedEntry to that object or to { file: null } so consumers that access .file never see undefined.packages/backend/src/core/NoteDraftService.ts-58-76 (1)
58-76:⚠️ Potential issue | 🟠 Major下書き上限チェックが競合で破れます。
countBy()とinsertOne()が分離されているので、同一ユーザーの同時createが並ぶと全リクエストが上限未満と判定して超過保存できます。上限を仕様として守るなら、ここはユーザー単位で排他するか、DB/トランザクション側で原子的に制約してください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/NoteDraftService.ts` around lines 58 - 76, The create method in NoteDraftService (create, noteDraftsRepository.countBy, noteDraftsRepository.insertOne, roleService.getUserPolicies, idService.gen) is racy because countBy and insertOne are separate; fix it by enforcing the draft-limit atomically: either perform the check+insert inside a single DB transaction that uses a row- or table-level lock for the user's drafts (or serializable isolation), or add a DB-enforced constraint (e.g., per-user draft counter or unique indexed slot) and handle violation errors to return the IdentifiableError; update create to attempt the atomic operation and fall back to translating DB constraint/transaction failures into the same "Too many drafts" IdentifiableError.packages/backend/src/core/NoteDraftService.ts-143-149 (1)
143-149:⚠️ Potential issue | 🟠 Major
visibleUserIdsの存在確認が未完了です。ここは
findBy()の結果を捨てているので、存在しない ID が混ざってもそのまま保存されます。宛先付き下書きとしては壊れた状態を持ててしまうので、少なくとも取得件数の一致は確認したいです。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/NoteDraftService.ts` around lines 143 - 149, The code fetches visibleUsers via this.usersRepository.findBy(...) but doesn't verify all requested visibleUserIds were found, allowing drafts to reference non-existent users; update NoteDraftService to validate that when data.visibleUserIds is non-empty the returned visibleUsers length matches data.visibleUserIds.length (or compare sets of ids), and if there are missing ids either throw a validation error (e.g., BadRequest/ValidationError) or return a clear failure so the draft is not saved with missing recipients; reference the variables visibleUserIds, visibleUsers and the call this.usersRepository.findBy to locate where to add this check.packages/backend/src/core/NoteDraftService.ts-82-99 (1)
82-99: 🛠️ Refactor suggestion | 🟠 Major部分更新の検証が既存値を見ていません。
validate(me, data)は patch だけを見るので、既存のreplyId/renoteId/channelIdと組み合わせた不正状態を作れます。たとえば direct への返信を保持したままvisibilityだけ広げてもここを通ります。更新時は既存 draft と patch をマージした状態を検証した方がよいです。💡 修正イメージ
public async update(me: MiLocalUser, draftId: MiNoteDraft['id'], data: Partial<NoteDraftOptions>): Promise<MiNoteDraft> { const draft = await this.noteDraftsRepository.findOneBy({ id: draftId, userId: me.id, }); if (draft == null) { throw new IdentifiableError('49cd6b9d-848e-41ee-b0b9-adaca711a6b1', 'No such note draft'); } - await this.validate(me, data); + const nextDraft = { + ...draft, + ...data, + }; + await this.validate(me, nextDraft); const updatedDraft = await this.noteDraftsRepository.createQueryBuilder().update() .set(data)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/NoteDraftService.ts` around lines 82 - 99, The update method calls validate(me, data) which only checks the patch and can allow invalid combinations when combined with existing draft fields; instead, merge the loaded draft (variable draft) with the incoming partial data to form the full new state and call validate(me, mergedData) before persisting; update the code path around update(...) / validate(...) (use draft and data to produce mergedData) so validation sees replyId/renoteId/channelId plus any changed visibility, then proceed with the repository update.packages/backend/src/core/ChannelMutingService.ts-8-10 (1)
8-10:⚠️ Potential issue | 🟠 Major期限切れミュートがキャッシュ上では有効なまま残ります。
list()は期限切れを除外していますが、mutingChannelsCacheのfetcherは全件取得なので、expiresAtを過ぎたミュートもisMuted()やタイムライン除外で効き続けます。クリーンアップが走るまで設定より長く効いてしまうので、キャッシュ生成時点で期限切れを落とす必要があります。💡 修正案
-import { Brackets, In } from 'typeorm'; +import { Brackets, In, IsNull, MoreThan } from 'typeorm'; @@ fetcher: (userId) => this.channelMutingRepository.find({ - where: { userId: userId }, + where: [ + { userId, expiresAt: IsNull() }, + { userId, expiresAt: MoreThan(new Date()) }, + ], select: ['channelId'], }).then(xs => new Set(xs.map(x => x.channelId))),Also applies to: 32-40
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/ChannelMutingService.ts` around lines 8 - 10, The cache fetcher for mutingChannelsCache must exclude expired mutes so they don't remain effective; update the fetcher (the function that currently calls the repository method that returns all MiChannelMuting records) to filter out records where expiresAt is set and expiresAt <= now (use the same expiry logic as ChannelMutingService.list()), so mutingChannelsCache, isMuted(), and any timeline exclusion use only non-expired MiChannelMuting entries when populating the cache and its keys.packages/backend/src/core/NoteCreateService.ts-273-320 (1)
273-320:⚠️ Potential issue | 🟠 Majorrenote 対象の可視性チェックが reply 側より弱いです。
fetchAndCreate()はreplyでは Line 334 のisVisibleForMe()を通していますが、renote側には同等の検証がありません。今のままだと、ID さえ分かれば非表示のノートやアクセス不可なチャンネル内ノートを quote/renote できる経路になります。renote取得後にもreplyと同じ可視性チェックを入れるべきです。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/NoteCreateService.ts` around lines 273 - 320, The renote visibility checks in NoteCreateService (inside the renote handling block) are weaker than the reply path; after fetching renote (the variable renote in fetchAndCreate()), call the same visibility validation used for replies (the isVisibleForMe() flow used around Line 334 in fetchAndCreate()) so hidden notes, notes in access-restricted channels, or otherwise non-visible renotes cannot be quoted/renoted; if the helper function isVisibleForMe or its equivalent exists, invoke it with renote and the current user and throw the same IdentifiableError used for reply visibility failures when it returns false, otherwise inline the same checks (channel access, specified/direct visibility, blocking) used by the reply branch to reject non-visible renotes.locales/en-US.yml-3275-3275 (1)
3275-3275:⚠️ Potential issue | 🟠 Major空文字のままだとカスタム絵文字管理 UI の文言が消えます。
searchLimitと確認ダイアログ文言が''のままなので、英語ロケールではラベルや説明が空表示になります。zh-TW 側には値が入っているので、未確定ではなく入れ忘れに見えます。💡 修正案
_gridCommon: - searchLimit: '' + searchLimit: 'Number of items to display' @@ - confirmChangeView: '' + confirmChangeView: 'Change the display mode?' confirmMovePage: 'Would you like to move pages?' - confirmResetDescription: '' + confirmResetDescription: 'All current changes will be reset.'Also applies to: 3300-3302
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/en-US.yml` at line 3275, The English locale has empty strings for the custom emoji UI keys (notably searchLimit and the confirmation dialog keys near the other empty entries) causing labels/descriptions to disappear; update the en-US translations for searchLimit and the empty confirmation dialog keys to non-empty English strings (use the corresponding zh-TW values as reference) so the UI shows proper labels and dialog text, ensuring keys like searchLimit and the adjacent confirmation dialog keys are populated consistently.locales/ko-KR.yml-859-859 (1)
859-859:⚠️ Potential issue | 🟠 Majorこのキーは「サーバー再起動必須」なのに、文言が「リロード」になっています。
管理者がブラウザ更新だけで反映されると誤解します。運用手順を誤らせるので、再起動必須であることを明示した方がよいです。
修正案
-needToRestartServerToApply: "변경 사항은 새로고침이 필요합니다." +needToRestartServerToApply: "변경 사항을 적용하려면 서버를 재시작해야 합니다."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ko-KR.yml` at line 859, The translation for the key needToRestartServerToApply currently says "변경 사항은 새로고침이 필요합니다." which implies a browser reload; update the value to explicitly state a server restart is required (e.g., "변경 사항을 적용하려면 서버를 재시작해야 합니다.") so the meaning matches the key needToRestartServerToApply and avoids operator confusion.
f2e2059 to
65d6c49
Compare
|
@coderabbitai fullreview |
|
🧠 Learnings used✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 1
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
packages/backend/src/core/MfmService.ts (1)
105-112:⚠️ Potential issue | 🔴 Critical
new URL(href)に try-catch がなく、不正なURLで例外が発生します。
hrefが不正なURL(相対パス、javascript:スキーム、壊れた文字列など)の場合、new URL(href)はTypeErrorをスローし、HTML解析が中断されます。
toHtmlメソッド(lines 375-380, 389-394, 417-422)では try-catch で適切に処理されていますが、ここでは処理されていません。🐛 例外処理を追加する修正案
if (part.length === 2 && href) { //#region ホスト名部分が省略されているので復元する - const acct = `${txt}@${(new URL(href)).hostname}`; - text += acct; + try { + const acct = `${txt}@${(new URL(href)).hostname}`; + text += acct; + } catch { + text += txt; + } //#endregion } else if (part.length === 3) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/MfmService.ts` around lines 105 - 112, In MfmService.ts inside the logic that builds acct with `const acct = `${txt}@${(new URL(href)).hostname}`;` wrap the `new URL(href)` call in a try-catch (or validate `href` first) so invalid/relative/javascript: URLs don't throw; on error fallback to not appending the hostname (i.e., treat as plain `txt`) or skip this branch, and log/debug as appropriate; mirror the error-handling approach used by the `toHtml` method to ensure parsing continues safely when `href` is malformed..github/workflows/test-production.yml (2)
51-56:⚠️ Potential issue | 🟠 MajorSARIF アップロード権限が足りません。
このジョブは Code Scanning へ SARIF を送っていますが、
permissionsにsecurity-events: writeが必要です。現状だとアップロードが失敗してもcontinue-on-error: trueで埋もれてしまいます。修正案
permissions: contents: read + security-events: write🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/test-production.yml around lines 51 - 56, The CodeQL SARIF upload step is missing the required permissions so the upload will fail silently; update the workflow so the job (or workflow) includes permissions: security-events: write and remove or reconsider continue-on-error: true on the github/codeql-action/upload-sarif@v4 step (or make failures visible) so SARIF upload of snyk.sarif can succeed and surface errors instead of being swallowed.
44-45:⚠️ Potential issue | 🟠 Major
snyk/actions/nodeを@masterブランチ参照ではなく、固定リファレンスで指定してください。
@masterのようなブランチ参照は浮動参照(floating reference)であり、GitHub のセキュリティガイダンスでは非推奨です。upstreamが更新されると予期しない挙動変更が発生し、スキャン結果の再現性が低下します。また、アクションのリポジトリが侵害された場合、悪意のあるコードが実行される供給チェーン攻撃のリスクがあります。GitHub の公式推奨に従い、完全なコミット SHA(40文字)または リリースタグに固定してください。特にSNYK_TOKENシークレットを使用するため、セキュリティ上重要です。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/test-production.yml around lines 44 - 45, The workflow step "Run Snyk to check for vulnerabilities" currently references snyk/actions/node@master (a floating branch); replace that floating reference with a fixed release tag or a full commit SHA (40-character) for the uses field (i.e., use snyk/actions/node@vX.Y.Z or snyk/actions/node@<commit-sha>) so the action is pinned and reproducible, and ensure the SNYK_TOKEN secret usage remains unchanged; update the single step that uses "snyk/actions/node" accordingly.locales/ja-KS.yml (1)
2597-2614:⚠️ Potential issue | 🟡 Minor通知タイプの列挙が新しい通知をカバーできていません。
直前で
scheduledNotePosted/scheduledNotePostFailed/chatRoomInvitationReceived/createTokenの文言を追加しているのに、_notification._types側に対応ラベルがありません。通知設定やフィルタ画面だけ関西弁訳が抜けます。修正例
_types: all: "すべて" note: "あんたらの新規投稿" follow: "フォロー" mention: "あんた宛て" reply: "リプライ" renote: "リノート" quote: "引用" reaction: "ツッコミ" pollEnded: "アンケートが終了したで" + scheduledNotePosted: "予約ノート投稿" + scheduledNotePostFailed: "予約ノート投稿失敗" receiveFollowRequest: "フォロー許可してほしいみたいやで" followRequestAccepted: "フォローが受理されたで" roleAssigned: "ロールが付与された" + chatRoomInvitationReceived: "チャットルーム招待" achievementEarned: "実績の獲得" exportCompleted: "エクスポート終わった" login: "ログイン" + createToken: "アクセストークン作成" test: "通知テスト" app: "連携アプリからの通知や"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ja-KS.yml` around lines 2597 - 2614, The _types map in locales/ja-KS.yml is missing labels for recently added notification keys; add Kansai-dialect labels for scheduledNotePosted, scheduledNotePostFailed, chatRoomInvitationReceived, and createToken inside the _notification._types (refer to the existing keys like note, follow, mention) so the notification settings and filter UI show localized text—ensure each new key uses the same style/terminology as nearby entries (e.g., casual Kansai phrasing) and matches the key names exactly.locales/de-DE.yml (1)
1635-1669:⚠️ Potential issue | 🟡 Minorde-DE の管理/セットアップ系キーがまだ揃っていません。
同PR内の
locales/zh-CN.ymlにはあるscheduledNoteLimit,remoteNotesCleaning*,restartServerSetupWizardConfirm_*,entrancePageStyle*,remoteContentsCleaning*などがこのファイルには見当たりません。このままだとロール設定、サーバー設定、セットアップウィザードの新項目だけ de-DE で未翻訳表示になります。Also applies to: 2010-2014, 3084-3125
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/de-DE.yml` around lines 1635 - 1669, The German locale is missing several server/setup keys present in zh-CN; add the missing keys (e.g., scheduledNoteLimit, remoteNotesCleaning*, remoteContentsCleaning*, restartServerSetupWizardConfirm_*, entrancePageStyle*, and any other keys present in locales/zh-CN.yml but absent here) into the _serverSettings block (and corresponding nested keys like _userGeneratedContentsVisibilityForVisitor if applicable), using the same key names and providing German translations for each new entry so the new role/server/setup and setup-wizard items display in de-DE. Ensure keys with wildcard groups (remoteNotesCleaning*, remoteContentsCleaning*) are fully expanded and translated to mirror the zh-CN structure and keep YAML indentation and quoting consistent with surrounding entries.
♻️ Duplicate comments (1)
packages/backend/package.json (1)
24-29:⚠️ Potential issue | 🟡 Minor
jest:fedスクリプトにNODE_ENV=testが設定されていませんLine 26 の
jest:fedスクリプトのみcross-env NODE_ENV=testが設定されていません。他の Jest スクリプト(jest、jest:e2e、jest-and-coverageなど)はすべてNODE_ENV=testを指定しています。コード内でprocess.env.NODE_ENV === 'test'をチェックしている箇所があるため、テスト環境の不整合が発生する可能性があります。🔧 修正案
- "jest:fed": "node ./jest.js --forceExit --config jest.config.fed.cjs", + "jest:fed": "cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.fed.cjs",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/package.json` around lines 24 - 29, The "jest:fed" npm script is missing NODE_ENV=test which can cause test-environment mismatches; update the "jest:fed" entry in package.json (the script named jest:fed) to prepend "cross-env NODE_ENV=test" so it matches the other Jest scripts (jest, jest:e2e, jest-and-coverage) and ensures process.env.NODE_ENV === 'test' during federated Jest runs.
🟠 Major comments (29)
packages/backend/assets/LICENSE_MisskeyHQ-13-21 (1)
13-21:⚠️ Potential issue | 🟠 Major許諾条件が相互に矛盾しています。
Line 13 では再配布・再利用を全面的に禁止していますが、Line 21 では notice 同梱を条件に
use, copy, and publishを許可しています。publishは実質的に再配布を含み得るため、このままだと downstream が同梱・配布してよいのか判断できず、ライセンス解釈がぶれます。禁止したい行為と許可したい行為を一つの方針に揃えて、条文を整理したほうがよいです。整合させるための最小修正例
-are owned by MisskeyHQ and are subject to copyright, with all rights reserved by MisskeyHQ. These files may not be redistributed, modified, or reused in any way without explicit permission from MisskeyHQ. +are owned by MisskeyHQ and are subject to copyright, with all rights reserved by MisskeyHQ. These files may be used, copied, and published only under the terms below. -Permission is hereby granted to the user of this digital asset and related documentation to use, copy, and publish the asset for any purpose, provided that this notice and disclaimer of warranty appears in all copies. The user is expressly prohibited from distributing, sublicensing, selling, reselling, or transferring the asset for profit. Any form of modification or tampering with the asset is strictly prohibited. +Permission is hereby granted to use, copy, and publish the asset, provided that this notice and disclaimer of warranty appears in all copies. Modification is prohibited. Distribution for profit, sublicensing, selling, reselling, or transferring the asset is prohibited unless explicitly permitted by MisskeyHQ.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/assets/LICENSE_MisskeyHQ` around lines 13 - 21, The license contains contradictory clauses: one sentence forbids redistribution/modification/reuse while a later paragraph ("Permission is hereby granted to the user... to use, copy, and publish the asset for any purpose, provided that this notice... appears in all copies") appears to permit publishing; resolve this by choosing a single policy and rewriting the conflicting phrases accordingly — either (A) make redistribution allowed under the stated terms by replacing "These files may not be redistributed, modified, or reused in any way without explicit permission" with a scoped restriction that permits redistribution and publication only when the notice is retained and commercial resale/sublicensing is prohibited, or (B) tighten the permission paragraph by removing "publish" and adding explicit prohibitions on redistribution/sublicensing to match the original ban; update the sentences containing "These files may not be redistributed, modified, or reused..." and "Permission is hereby granted..." so they state a single, consistent rule (reference these exact phrases to locate and edit).packages/backend/migration/1753868431598-remove_note_constraints.js-14-16 (1)
14-16:⚠️ Potential issue | 🟠 Majorロールバック時に孤児参照で
ADD CONSTRAINTが失敗します。
up()適用後はreplyId/renoteIdが孤児化し得るので、Line 15-16 の外部キー再作成は既存データ検証で失敗します。これだと一度でも不整合データが入った環境ではdown()が実質使えません。制約を戻す前に孤児参照を掃除してください。修正例
async down(queryRunner) { + await queryRunner.query(` + UPDATE "note" AS child + SET "replyId" = NULL + WHERE "replyId" IS NOT NULL + AND NOT EXISTS ( + SELECT 1 + FROM "note" AS parent + WHERE parent."id" = child."replyId" + ) + `); + await queryRunner.query(` + UPDATE "note" AS child + SET "renoteId" = NULL + WHERE "renoteId" IS NOT NULL + AND NOT EXISTS ( + SELECT 1 + FROM "note" AS parent + WHERE parent."id" = child."renoteId" + ) + `); await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_17cb3553c700a4985dff5a30ff5" FOREIGN KEY ("replyId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_52ccc804d7c69037d558bac4c96" FOREIGN KEY ("renoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1753868431598-remove_note_constraints.js` around lines 14 - 16, down(queryRunner) currently re-adds foreign keys "FK_17cb3553c700a4985dff5a30ff5" and "FK_52ccc804d7c69037d558bac4c96" without cleaning orphaned replyId/renoteId, so ADD CONSTRAINT will fail if orphan rows exist; before the two ALTER TABLE calls in down(), run SQL via queryRunner.query to remove or fix orphaned references (e.g., DELETE or UPDATE to set replyId/renoteId to NULL for rows where replyId/renoteId NOT IN (SELECT id FROM "note") or where referenced note is missing) so the subsequent ALTER TABLE ... ADD CONSTRAINT statements succeed.cypress/e2e/basic.cy.ts-81-82 (1)
81-82:⚠️ Potential issue | 🟠 Majorこの変更で重複ユーザー名テストが別条件で通るようになります。
Line 82 で招待コードが送信可能条件に加わったので、下の
signup with duplicated usernameは招待コード未入力の時点で submit が無効のままです。つまり、いまの最終アサーションは「ユーザー名重複」ではなく「招待コード不足」で成立してしまい、重複チェックの回帰を見逃します。あちらのケースにも有効な招待コードを入れて、重複ユーザー名固有の失敗を検証してください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cypress/e2e/basic.cy.ts` around lines 81 - 82, テスト "signup with duplicated username" が招待コード未入力のままでは最終アサーションが「招待コード不足」を検証してしまうので、同テスト内で '[data-cy-signup-invitation-code] input' に有効な招待コードを入力して '[data-cy-signup-submit]' が送信可能になる状態を作ってから重複ユーザ名固有のエラーメッセージを検証するように修正してください(テスト名: signup with duplicated username、確認対象セレクタ: [data-cy-signup-submit] と重複エラーメッセージ)。cypress/e2e/basic.cy.ts-35-41 (1)
35-41:⚠️ Potential issue | 🟠 Major
cy.wait('@update-meta')だけでは API エラーを見逃す可能性があります。
cy.wait()はインターセプトされた通信の完了しか待つため、/api/admin/update-metaが 4xx/5xx で応答しても待機は完了してしまいます。初期セットアップの失敗を検出するには、Line 41 で成功ステータスを検証してください。修正例
- cy.wait('@update-meta'); + cy.wait('@update-meta') + .its('response.statusCode') + .should('be.gte', 200) + .should('be.lt', 300);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cypress/e2e/basic.cy.ts` around lines 35 - 41, cy.wait('@update-meta') only waits for the request to finish but not that it succeeded; after the existing cy.wait('@update-meta') call, assert the intercepted response status is in the 2xx range by inspecting the alias's response (the '@update-meta' alias from cy.intercept) and failing the test if statusCode is >= 400; update the test around the selectors used (cy.get('[data-cy-next]', '[data-cy-server-name] input', '[data-cy-server-setup-wizard-apply]') and the cy.wait('@update-meta') line to include an assertion on the intercepted response.statusCode (or response.status) to ensure the setup API returned success.packages/backend/migration/1752509043847-migration-cleanup.js-23-24 (1)
23-24:⚠️ Potential issue | 🟠 Major既存データを埋めずに
NOT NULLを追加すると移行で止まります。Line 24 は、長く nullable だった
flash.visibilityにそのままSET NOT NULLをかけています。既存環境にNULLが 1 件でも残っているとこの migration 全体が失敗するので、先に backfill するUPDATEかNULLチェックを挟いでから制約を付けた方が安全です。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1752509043847-migration-cleanup.js` around lines 23 - 24, The migration directly runs ALTER TABLE "flash" ALTER COLUMN "visibility" SET NOT NULL which will fail if any existing rows have NULL; before calling queryRunner.query for the NOT NULL change (in 1752509043847-migration-cleanup.js), add a safe backfill or guard: run an UPDATE to set a concrete default for flash.visibility for rows where visibility IS NULL (or run a SELECT check and abort with a clear error), then perform the ALTER; reference the ALTER TABLE statement and the "flash.visibility" column so you update the correct rows and ensure the NOT NULL constraint can be applied safely.packages/backend/migration/1752509043847-migration-cleanup.js-36-39 (1)
36-39:⚠️ Potential issue | 🟠 MajorLine 56 の
down()メソッドでuserIdコメントが正しく復元されていません。
up()では replyId、renoteId、channelId と同じく userId にコメントを設定していますが、down()で userId だけが他の 3 カラムと異なる復元方法になっています。元の
createNoteDraftmigration には COMMENT ON COLUMN ステートメントがないため、元の状態ではすべてのコメントが NULL です。したがってdown()メソッドでは以下のように統一されるべきです:
replyId→ NULL ✓renoteId→ NULL ✓channelId→ NULL ✓userId→ NULL (現在は'The ID of author.'となっており不正)Line 56 を
await queryRunner.query(\COMMENT ON COLUMN "note_draft"."userId" IS NULL`);` に変更してください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1752509043847-migration-cleanup.js` around lines 36 - 39, In the down() method of the migration (function down), restore the userId column comment the same way as replyId/renoteId/channelId by setting it back to NULL; replace the current incorrect restoration that reassigns 'The ID of author.' with a COMMENT ON COLUMN "note_draft"."userId" IS NULL statement so all four columns revert to no comment consistently.packages/backend/eslint.config.js-28-28 (1)
28-28: 🛠️ Refactor suggestion | 🟠 Major
no-unused-varsを完全に無効化すると、未使用変数の検出機構がなくなります。packages/backend の tsconfig.json で
noUnusedLocalsとnoUnusedParametersが 明示的にfalseに設定されているため、TypeScript コンパイラ側の保護がありません。ESLint ルールも無効化されているので、未使用の import / local / parameter を一切検出できない状態になっています。以下のいずれかの対応が必須です:
- ignore pattern(例:
^_プレフィックス)を活用してルールを部分的に無効化する- または、tsconfig で
noUnusedLocals/noUnusedParametersをtrueに設定する🔧 提案:ignore pattern を使った改善案
- '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': ['warn', { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }],🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/eslint.config.js` at line 28, The '@typescript-eslint/no-unused-vars' rule is fully turned off in packages/backend/eslint.config.js which, combined with tsconfig.json having noUnusedLocals and noUnusedParameters set to false, removes all unused-variable detection; fix by either re-enabling the ESLint rule with an ignore pattern (e.g. change the '@typescript-eslint/no-unused-vars' setting from 'off' to a configured error level and set varsIgnorePattern/argsIgnorePattern/caughtErrorsIgnorePattern to '^_' so intentionally underscored identifiers are exempt) or enable TypeScript compiler checks by setting noUnusedLocals and noUnusedParameters to true in the package's tsconfig.json; apply one of these changes and run lint/tsc to verify unused imports/locals/params are reported.packages/backend/src/core/AiService.ts-73-75 (1)
73-75:⚠️ Potential issue | 🟠 Major
tf.node.decodeImage()経由での形式対応制限を確認して対応すること
@tensorflow/tfjs-nodeのtf.node.decodeImage()は BMP、GIF、JPEG、PNG のみ対応で、WebP・AVIF には非対応です。Line 74-75 は MIME 情報を使わず完全にこの API に依存するため、WebP/AVIF を受け付けているシステムにおいて、mime パラメータなしの呼び出しまたは Buffer 渡しの場合、これらの形式は無言で失敗します。後続の catch ブロック(86 行)で例外を吸収しているため、呼び出し側は null 返却を見ることになります。メソッドのオーバーロード(37-38 行)では mime を省略可能なため、実際に WebP/AVIF が予期せず mime なし で渡される可能性があります。
64-72 行の Sharp 経由パスと 74-75 行の tf.node パスで形式サポートの整合性を保証するか、mime なし呼び出しは Sharp 処理へ統一するかの対応が必要です。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/AiService.ts` around lines 73 - 75, The tf.node.decodeImage(...) path (tf.node.decodeImage in AiService) only supports BMP/GIF/JPEG/PNG so when mime is omitted or a Buffer for WebP/AVIF is passed it silently fails and gets swallowed by the catch; update the method to detect the image type and route unsupported formats to the Sharp branch (or use Sharp to convert WebP/AVIF to a supported format before calling tf.node.decodeImage) and/or require/validate mime when calling tf.node.decodeImage; specifically: when handling the sourceOrPath branch that currently calls tf.node.decodeImage(buffer, 3) check the MIME (or probe the buffer with a file-type detector) and if it's WebP/AVIF use the existing Sharp processing path (or convert the buffer to PNG/JPEG via Sharp) instead of calling tf.node.decodeImage, and ensure errors from tf.node.decodeImage are not silently swallowed by the catch so callers can distinguish unsupported formats from other failures.packages/backend/src/core/NoteCreateService.ts-253-255 (1)
253-255:⚠️ Potential issue | 🟠 Major
visibleUserIdsの欠落を黙って無視しないでください。
findBy()は存在するユーザーだけ返すので、今の実装だと typo や削除済み ID を含んでも一部の宛先が静かに落ちます。fileIdsと同じように、重複除去後の要求件数と取得件数を突き合わせて不足時はエラーにした方が安全です。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/NoteCreateService.ts` around lines 253 - 255, The code silently ignores missing/typo/deleted IDs when resolving visibleUserIds by using usersRepository.findBy (assigned to visibleUsers); instead deduplicate data.visibleUserIds, fetch users with findBy, then compare the number of unique requested IDs to the number of fetched users and throw a validation/error if they differ (similar to how fileIds are validated). Update NoteCreateService to perform the dedupe + count check and raise a clear error referencing data.visibleUserIds when any requested user IDs are missing.packages/backend/src/core/NoteCreateService.ts-273-320 (1)
273-320:⚠️ Potential issue | 🟠 Majorリノート対象にも
isVisibleForMe()相当の可視性チェックが必要です。返信側は Line 334 で
this.noteEntityService.isVisibleForMe()を通していますが、こちらは存在確認と一部の可視性分岐だけです。チャンネル制約などisVisibleForMe()側の条件を素通りできるので、リノート対象にも同じ判定を入れてください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/NoteCreateService.ts` around lines 273 - 320, The renote path in NoteCreateService currently skips the full visibility rules; update the renote handling block (where variable renote is fetched) to call this.noteEntityService.isVisibleForMe(renote, user, { channelId: data.channelId, ... }) (same check used later at Line 334) and if it returns false, throw the appropriate IdentifiableError(s) as the visibility gate would — ensuring channel constraints, specified/direct and other visibility rules are enforced for renote targets just like for the main note.idea/MkAnimatedBg.dotted.vue-142-150 (1)
142-150:⚠️ Potential issue | 🟠 MajorChromatic 用の静止画が毎回変わります。
isChromatic()分岐でもu_seedをMath.random()で初期化しているので、1 枚描画の結果が実行ごとに変わります。これだと Chromatic のスナップショットが不安定になります。修正案
- gl.uniform1f(u_seed, Math.random() * 1000); + gl.uniform1f(u_seed, isChromatic() ? 0 : Math.random() * 1000);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@idea/MkAnimatedBg.dotted.vue` around lines 142 - 150, The Chromatic snapshots are unstable because u_seed is being set with Math.random() before the isChromatic() branch; change the logic so that when isChromatic() returns true you set a deterministic seed instead of Math.random() (e.g., a fixed constant or a seed derived from props), i.e. update the code that calls gl.uniform1f(u_seed, ...) (and/or the isChromatic() branch that currently calls gl.uniform1f(u_time, 0) then draw) to use a stable value for u_seed so each Chromatic render is repeatable..github/workflows/test-frontend.yml-42-45 (1)
42-45:⚠️ Potential issue | 🟠 Major
.node-version単独更新でこの workflow が走りません。Line 45 で Node.js の参照元を
.node-versionに変えていますが、Line 11-25 のpathsにそのファイルが入っていません。Node.js バージョンだけを更新した PR/commit ではこのジョブがスキップされ、変更自体を CI で検証できなくなります。修正案
on: push: branches: - main - next - beta - io - host paths: - packages/frontend/** # for permissions - packages/misskey-js/** + - .node-version - pnpm-lock.yaml - .github/workflows/test-frontend.yml - .github/misskey/test.yml pull_request: paths: - packages/frontend/** # for permissions - packages/misskey-js/** + - .node-version - pnpm-lock.yaml - .github/workflows/test-frontend.yml - .github/misskey/test.yml🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/test-frontend.yml around lines 42 - 45, The workflow uses actions/setup-node@v4.4.0 with node-version-file: '.node-version' but the workflow's on: push (paths) list does not include '.node-version', so updates to that file alone won't trigger the job; update the workflow's paths array (the on: push paths block referenced in lines around the top of the file) to include '.node-version' so changes to the file will trigger the job that runs the actions/setup-node step.packages/backend/scripts/dev.mjs-31-36 (1)
31-36:⚠️ Potential issue | 🟠 Major
killProc()が既に終了したプロセスで無限に待機します。
backendProcessがクラッシュなどで既に終了した状態でkillProc()が呼ばれると、Line 35 の Promise は新しいexitイベントを待ち続けて再起動ループが止まります。プロセスが実行中かどうかを確認してからkill()と待機を実行する必要があります。修正案
async function killProc() { if (backendProcess) { backendProcess.catch(() => {}); // backendProcess.kill()によって発生する例外を無視するためにcatch()を呼び出す - backendProcess.kill(); - await new Promise(resolve => backendProcess.on('exit', resolve)); + if (backendProcess.exitCode === null && backendProcess.signalCode === null) { + backendProcess.kill(); + await new Promise(resolve => backendProcess.once('exit', resolve)); + } backendProcess = undefined; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/scripts/dev.mjs` around lines 31 - 36, killProc() can hang waiting for an 'exit' event if backendProcess already exited; update killProc to first check the ChildProcess state (use backendProcess.exitCode !== null or backendProcess.killed / exitCode) and only call backendProcess.kill() and await the 'exit' event when the process is still running, otherwise skip kill()/waiting and simply clear backendProcess; reference symbols: killProc, backendProcess, backendProcess.kill(), and the 'exit' event so the fix guards before killing and awaiting.packages/backend/migration/1761569941833-add-channel-muting.js-17-17 (1)
17-17:⚠️ Potential issue | 🟠 Major
channel_mutingに一意制約を付けてください
("userId", "channelId")が非 UNIQUE のため、同一ユーザー・同一チャンネルの重複 mute が並行実行で作成され得ます。解除や判定の一貫性が崩れる原因になります。💡 修正例
- await queryRunner.query(`CREATE INDEX "IDX_b96870ed326ccc7fa243970965" ON "channel_muting" ("userId", "channelId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b96870ed326ccc7fa243970965" ON "channel_muting" ("userId", "channelId") `);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1761569941833-add-channel-muting.js` at line 17, 現在のCREATE INDEX "IDX_b96870ed326ccc7fa243970965" ON "channel_muting" ("userId", "channelId")は非 UNIQUE のため重複行が作成され得ます。修正手順は、まず重複する (userId, channelId) ペアを削除または統合して一意性を担保し、その後既存のインデックス "IDX_b96870ed326ccc7fa243970965" を削除してから同列に対してUNIQUE制約を付与する(例: ALTER TABLE "channel_muting" ADD CONSTRAINT ... UNIQUE ("userId","channelId") もしくはCREATE UNIQUE INDEX ... ON "channel_muting" ("userId","channelId"))ようにマイグレーションを更新してください。必ずデータ移行(重複削除)をマイグレーション内で行い、マイグレーションが失敗しないようトランザクションか安全な順序で実行してください。packages/backend/src/core/ChannelFollowingService.ts-59-76 (1)
59-76:⚠️ Potential issue | 🟠 Major
idOnly: trueのときの戻り値型が正確ではありません。線 68 では
Promise<MiChannel[]>を返す型宣言になっていますが、線 74-76 のidOnly: true分岐では{ id: string }だけのオブジェクトをMiChannelにキャストして返しています。JSDoc でも「ID以外のフィールドに値がセットされなくなる」と明記されているのに、型システムがこの制約を表現していません。現在の呼び出し箇所はすべて
.then(x => x.map(x => x.id))でidだけを抽出しているため実運用上の問題はありませんが、型安全性のため TypeScript のオーバーロードを使い、idOnly: trueのときはArray<Pick<MiChannel, 'id'>>を返すように修正してください。修正例
+ public async list( + params: { + requestUserId: MiUser['id'], + }, + opts: { + idOnly: true; + }, + ): Promise<Array<Pick<MiChannel, 'id'>>>; + public async list( + params: { + requestUserId: MiUser['id'], + }, + opts?: { + idOnly?: false; + joinUser?: boolean; + joinBannerFile?: boolean; + }, + ): Promise<MiChannel[]>; public async list( params: { requestUserId: MiUser['id'], }, opts?: { idOnly?: boolean; joinUser?: boolean; joinBannerFile?: boolean; }, - ): Promise<MiChannel[]> { + ): Promise<MiChannel[] | Array<Pick<MiChannel, 'id'>>> { if (opts?.idOnly) { const q = this.channelFollowingsRepository.createQueryBuilder('channel_following') .select('channel_following.followeeId') .where('channel_following.followerId = :userId', { userId: params.requestUserId }); return q .getRawMany<{ channel_following_followeeId: string }>() - .then(xs => xs.map(x => ({ id: x.channel_following_followeeId } as MiChannel))); + .then(xs => xs.map(x => ({ id: x.channel_following_followeeId }))); } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/ChannelFollowingService.ts` around lines 59 - 76, The list method's declared return type Promise<MiChannel[]> is incorrect for the idOnly branch; add TypeScript overloads for ChannelFollowingService.list so that when called with opts: { idOnly: true } it returns Promise<Array<Pick<MiChannel,'id'>>> and the existing signature remains for the general case, then keep a single implementation (the current function body) that satisfies both overloads and returns the mapped raw rows as Array<Pick<MiChannel,'id'>> when opts?.idOnly is true and MiChannel[] otherwise; update the method declarations above the implementation accordingly (referencing ChannelFollowingService.list, MiChannel, and the opts.idOnly branch).packages/backend/src/cli/CommandService.ts-15-22 (1)
15-22:⚠️ Potential issue | 🟠 Major未使用の
loggerフィールドと import を削除してください。Line 15 の
private logger: Logger;と line 9 のimport type Logger from '@/logger.js';はコード内で一度も使用されていません。削除してください。修正例
import { Inject, Injectable } from '@nestjs/common'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; -import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { MetaService } from '@/core/MetaService.js'; `@Injectable`() export class CommandService { - private logger: Logger; - constructor( `@Inject`(DI.config) private config: Config,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/cli/CommandService.ts` around lines 15 - 22, Remove the unused Logger import and class field: delete the import line "import type Logger from '@/logger.js';" and the field declaration "private logger: Logger;" in the CommandService class (constructor and other methods do not reference it); ensure no other references to Logger remain in this file and run a quick typecheck/build to confirm no residual usages.packages/backend/src/core/NoteDraftService.ts-58-76 (1)
58-76:⚠️ Potential issue | 🟠 Major下書き上限チェックが非原子的です。
Line 60-65 の
countBy()と Line 72-76 のinsertOne()が分かれているので、同一ユーザーから並列にcreate()が来るとnoteDraftLimitを超えた件数を保存できます。上限を厳密に守るなら、同一トランザクション内で検査と挿入を行うか、ユーザー単位のロックが必要です。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/NoteDraftService.ts` around lines 58 - 76, The create method in NoteDraftService performs a non-atomic draft-limit check using roleService.getUserPolicies + noteDraftsRepository.countBy followed by noteDraftsRepository.insertOne, which allows race conditions; wrap the check and insert in a single atomic operation (use a DB transaction or a per-user mutex/lock) so the count and insert occur together, or enforce the limit at the database level and handle constraint errors; update create (and related idService.gen and validate usage) to run inside that transaction/lock and translate DB/locking failures into the existing IdentifiableError when the draft limit is exceeded.locales/id-ID.yml-403-403 (1)
403-403:⚠️ Potential issue | 🟠 Major
enableMcaptchaの訳文が空です。Line 403 のままだと mCaptcha の設定ラベルが空表示になり、何を有効化する項目なのか判別できません。
修正例
-enableMcaptcha: "" +enableMcaptcha: "Nyalakan mCaptcha"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/id-ID.yml` at line 403, The translation for the key enableMcaptcha is currently empty; update the id-ID locale entry for enableMcaptcha by providing an appropriate Indonesian label such as "Aktifkan mCaptcha" (or similar phrasing like "Mengaktifkan mCaptcha") so the mCaptcha setting displays a clear label; modify the enableMcaptcha value in locales id-ID.yml (key: enableMcaptcha) to the chosen translated string.packages/backend/src/core/NoteDraftService.ts-82-99 (1)
82-99:⚠️ Potential issue | 🟠 Major部分更新で既存ドラフトとの整合性チェックを回避できます。
Line 92 で
dataだけをvalidate()に渡しているため、既存のreplyId/renoteId/channelIdと組み合わせた制約が再評価されません。たとえば既存ドラフトが限定公開ノートへの返信を持っていても、visibilityだけを変更すれば Line 230-231 の制約をすり抜けた状態で保存できます。更新時は保存前にdraftとdataをマージした最終状態を検証してください。修正イメージ
- await this.validate(me, data); + await this.validate(me, { + ...draft, + ...data, + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/core/NoteDraftService.ts` around lines 82 - 99, The update flow calls validate(me, data) with only the partial update, which lets constraints involving existing draft fields (replyId/renoteId/channelId/visibility) be bypassed; before persisting in update() (the block that uses noteDraftsRepository.createQueryBuilder().update()), merge the existing draft and the incoming data into a single finalDraft object (e.g., finalDraft = { ...draft, ...data } but only with DTO fields) and pass that merged object to validate(me, finalDraft), then proceed to save; ensure validate() sees the complete final state so cross-field checks on replyId/renoteId/channelId/visibility run correctly.locales/ja-KS.yml-3001-3028 (1)
3001-3028:⚠️ Potential issue | 🟠 Majorサーバー初期設定ウィザードのキーがまだ揃っていません。
このブロックは追加されていますが、並行して追加されている他ロケール相当の
serverSetting、_use.single/group/open、_scale.small/medium/large、applyTheseSettingsなどが見当たりません。新しいセットアップフローで関西弁と fallback が混在表示になります。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ja-KS.yml` around lines 3001 - 3028, The _serverSetupWizard block was added but the complementary keys (e.g., serverSetting, _use.single/_use.group/_use.open, _scale.small/_scale.medium/_scale.large, applyTheseSettings) are missing causing mixed Kansai and fallback text; update locales/ja-KS.yml to include the full set of server setup keys that other locales define by copying/translating the corresponding entries (serverSetting, _use.*, _scale.*, applyTheseSettings, etc.) from a complete locale (like ja or en) into the ja-KS namespace so the new setup flow has a consistent, complete keyset and avoids fallback mixing.locales/it-IT.yml-2576-2577 (1)
2576-2577:⚠️ Potential issue | 🟠 Major
disableFederationの訳が別機能に見えます。
Gestisci la federazioneだと「連合を管理する」意味になっていて、投稿を他サーバーへ送らない設定だと伝わりません。公開範囲まわりの文言なので、このままだと誤操作を招きます。修正例
- disableFederation: "Gestisci la federazione" + disableFederation: "Disabilita la federazione"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/it-IT.yml` around lines 2576 - 2577, The label disableFederation currently reads like "manage federation" and is misleading; update the translation for disableFederation to a concise phrase that clearly indicates turning federation off (e.g., use an Italian label meaning "Disable federation" or "Do not send activities to other instances"), and ensure disableFederationDescription (the explanatory text) remains consistent with that meaning so the UI clearly conveys that posts will not be sent to remote instances.locales/en-US.yml-3275-3275 (1)
3275-3275:⚠️ Potential issue | 🟠 Major空文字のまま追加しないでください。
Line 3275 / 3300 / 3302 が
''のままなので、検索条件ラベルと確認ダイアログ本文が空表示になります。locales/en-US.ymlはフォールバック元でもあるため、このままだと無言 UI が出ます。Also applies to: 3300-3302
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/en-US.yml` at line 3275, The YAML keys currently set to empty strings (e.g., searchLimit and the other two empty entries) must not be left as '' — replace each empty value with a meaningful English string: provide a concise search condition label for searchLimit and proper confirmation dialog text for the confirmation-body keys so the UI fallback isn’t blank; locate the empty entries by their key names (searchLimit and the two other blank keys in the same block) and update their values with appropriate human-readable phrases.locales/ru-RU.yml-242-244 (1)
242-244:⚠️ Potential issue | 🟠 Majorサイレンス設定の説明が原文と別の挙動を示しています。
Line 242-244 は
locales/en-US.ymlのsilencedInstancesDescription/mediaSilencedInstancesDescriptionと比べて、「silenced」を「blocked」と説明したり、follow request 周りの意味が崩れています。管理画面の説明としては誤設定を招くので、原文どおりの挙動に合わせて訳し直した方がよいです。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ru-RU.yml` around lines 242 - 244, The Russian translations for silencedInstancesDescription and mediaSilencedInstancesDescription currently change the meaning (using “blocked” and altering follow-request behavior); update these two strings so their wording exactly reflects the original en-US entries: keep the concept “silenced” (not “blocked”), preserve the original notes about how accounts from listed instances are treated (what actions are restricted, e.g., follow/follow request/local account behavior) and that media access is disabled for those instances; replace the current Russian text for the keys silencedInstancesDescription and mediaSilencedInstancesDescription with faithful translations that mirror the en-US messages.locales/ru-RU.yml-460-461 (1)
460-461:⚠️ Potential issue | 🟠 Major空文字の
addModerationNoteは埋めてください。Line 461 が空なので、モデレーションノート追加のボタン/メニューが無表示になります。
🛠️ 修正案
-addModerationNote: "" +addModerationNote: "Добавить заметку модератора"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ru-RU.yml` around lines 460 - 461, The translation key addModerationNote in locales/ru-RU.yml is empty, causing the "add moderation note" button/menu to not render; update the addModerationNote value to a proper Russian translation (e.g., "Добавить заметку модерации" or similar) to ensure the UI label appears, keeping moderationNoteDescription as-is and only replacing the empty string for addModerationNote.locales/ru-RU.yml-1176-1176 (1)
1176-1176:⚠️ Potential issue | 🟠 Major招待コード上限の案内が逆の意味になっています。
Line 1176 の
locales/en-US.ymlは「{time} に {limit} へリセットされる」ですが、現在の訳文は「無制限に作成できる」と読めます。利用上限を誤案内するので、ここは原文に寄せてください。🛠️ 修正案
-inviteLimitResetCycle: "За определенное {time} Вы можете создать неограниченное количество пригласительных кодов {limit} " +inviteLimitResetCycle: "Этот лимит будет сброшен до {limit} в {time}."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ru-RU.yml` at line 1176, The translation for the key inviteLimitResetCycle in locales/ru-RU.yml incorrectly says users can create unlimited invites; update the string to match locales/en-US.yml semantics so it indicates the limit will reset to {limit} after {time} (e.g., "Через {time} лимит будет сброшен до {limit}"), keeping the {time} and {limit} placeholders intact.locales/ru-RU.yml-1258-1260 (1)
1258-1260:⚠️ Potential issue | 🟠 MajorPasskey の導線が別の認証手段になっています。
Line 1258 は
signinWithPasskeyなのに「пароль」と表示され、Line 1260 も Passkey 検証失敗の文言として不自然です。認証手段のラベルがずれるとログイン導線を誤らせます。🛠️ 修正案
-signinWithPasskey: "Войдите в систему, используя свой пароль" +signinWithPasskey: "Войти с помощью ключа доступа" -passkeyVerificationFailed: "Ошибка проверка ключа доступа " +passkeyVerificationFailed: "Не удалось проверить ключ доступа"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ru-RU.yml` around lines 1258 - 1260, The Russian translations for authentication labels are incorrect: update signinWithPasskey to mention passkey (e.g., replace "Войдите в систему, используя свой пароль" with a phrase indicating passkey/ключ доступа), change unknownWebAuthnKey to explicitly reference WebAuthn (e.g., "Неизвестный ключ WebAuthn" or "Неизвестный WebAuthn-ключ"), and fix passkeyVerificationFailed grammar to "Ошибка проверки ключа доступа" (or similar natural Russian). Modify the values for signinWithPasskey, unknownWebAuthnKey, and passkeyVerificationFailed in the locales file to these corrected phrases.packages/backend/src/boot/cli.ts-18-49 (1)
18-49:⚠️ Potential issue | 🟠 Major
process.exit()前にapp.close()を通してくださいこのままだと Nest の application context を閉じずに終了するので、DB 接続や logger の flush がスキップされる可能性があります。
process.exit()直呼びではなく、終了コードを保持してfinallyでawait app.close()する形にした方が安全です。修正案
const app = await NestFactory.createApplicationContext(CommandModule, { logger: new NestLogger(), }); const commandService = app.get(CommandService); const command = process.argv[2] ?? 'help'; +let exitCode = 0; -switch (command) { - case 'help': { - console.log('Available commands:'); - console.log(' help - Displays this help message'); - console.log(' reset-captcha - Resets the captcha'); - break; - } - case 'ping': { - await commandService.ping(); - break; - } - case 'reset-captcha': { - await commandService.resetCaptcha(); - console.log('Captcha has been reset.'); - break; - } - default: { - console.error(`Unrecognized command: ${command}`); - console.error('Use "help" to see available commands.'); - process.exit(1); - } +try { + switch (command) { + case 'help': { + console.log('Available commands:'); + console.log(' help - Displays this help message'); + console.log(' reset-captcha - Resets the captcha'); + break; + } + case 'ping': { + await commandService.ping(); + break; + } + case 'reset-captcha': { + await commandService.resetCaptcha(); + console.log('Captcha has been reset.'); + break; + } + default: { + console.error(`Unrecognized command: ${command}`); + console.error('Use "help" to see available commands.'); + exitCode = 1; + } + } +} finally { + await app.close(); } -process.exit(0); +process.exitCode = exitCode;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/src/boot/cli.ts` around lines 18 - 49, The CLI currently calls process.exit() directly and doesn't close the Nest application context; refactor the top-level flow around the NestFactory.createApplicationContext(...) and the switch on process.argv so you capture an exitCode (default 0), run the command handlers (ping, resetCaptcha on commandService) inside try/catch to set a non-zero exitCode on error, and in a finally block await app.close() before calling process.exit(exitCode); reference the created app variable, CommandModule/NestFactory.createApplicationContext, the commandService usage, and the existing process.exit calls to locate where to change control flow.packages/backend/migration/js/migration-config.js-16-23 (1)
16-23:⚠️ Potential issue | 🟠 Major空の YAML や null ドキュメントで TypeErrors が発生します
yaml.load()がundefinedまたはnullを返す場合、ガード処理がないためconfig.disallowExternalApRedirectなどへのアクセス時に即座に TypeError が発生します。このエラーは??演算子の前段階で発生するため、デフォルト値に到達しません。オブジェクトに正規化してから各プロパティを読み取る必要があります。修正案
function loadConfigInternal() { - const config = yaml.load(fs.readFileSync(configYamlPath, 'utf-8')); + const loaded = yaml.load(fs.readFileSync(configYamlPath, 'utf-8')); + const config = loaded && typeof loaded === 'object' && !Array.isArray(loaded) ? loaded : {}; return { disallowExternalApRedirect: Boolean(config.disallowExternalApRedirect ?? false), proxyRemoteFiles: Boolean(config.proxyRemoteFiles ?? false), signToActivityPubGet: Boolean(config.signToActivityPubGet ?? true), } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/js/migration-config.js` around lines 16 - 23, loadConfigInternal calls yaml.load(...) and then directly reads properties like config.disallowExternalApRedirect, which throws when yaml.load returns null/undefined; normalize the result first (e.g. const cfg = (config && typeof config === 'object') ? config : {}) and then read cfg.disallowExternalApRedirect, cfg.proxyRemoteFiles, cfg.signToActivityPubGet so the null/undefined case falls back to the intended defaults.packages/backend/migration/1745378064470-composite-note-index.js-15-20 (1)
15-20:⚠️ Potential issue | 🟠 Major並行モードでも
DROP INDEXがブロッキングです
CREATE INDEX CONCURRENTLYにしても、既存 index の削除が通常のDROP INDEXのままだと、混雑時にnoteへの書き込みを止める可能性があります。並行モードでは drop 側もCONCURRENTLYに揃えた方が狙いに合います。さらに、down 側では既に
mayConcurrently変数が準備されているのに、DROP 時には使用されていません(CREATE 時には使われています)。修正案
async up(queryRunner) { const concurrently = isConcurrentIndexMigrationEnabled(); if (concurrently) { const hasValidIndex = await queryRunner.query(`SELECT indisvalid FROM pg_index INNER JOIN pg_class ON pg_index.indexrelid = pg_class.oid WHERE pg_class.relname = 'IDX_724b311e6f883751f261ebe378'`); if (hasValidIndex.length === 0 || hasValidIndex[0].indisvalid !== true) { - await queryRunner.query(`DROP INDEX IF EXISTS "IDX_724b311e6f883751f261ebe378"`); + await queryRunner.query(`DROP INDEX CONCURRENTLY IF EXISTS "IDX_724b311e6f883751f261ebe378"`); await queryRunner.query(`CREATE INDEX CONCURRENTLY "IDX_724b311e6f883751f261ebe378" ON "note" ("userId", "id" DESC)`); } } else { await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_724b311e6f883751f261ebe378" ON "note" ("userId", "id" DESC)`); @@ async down(queryRunner) { const mayConcurrently = isConcurrentIndexMigrationEnabled() ? 'CONCURRENTLY' : ''; - await queryRunner.query(`DROP INDEX IF EXISTS "IDX_724b311e6f883751f261ebe378"`); + await queryRunner.query(`DROP INDEX ${mayConcurrently} IF EXISTS "IDX_724b311e6f883751f261ebe378"`); await queryRunner.query(`CREATE INDEX ${mayConcurrently} "IDX_5b87d9d19127bd5d92026017a7" ON "note" ("userId")`); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/backend/migration/1745378064470-composite-note-index.js` around lines 15 - 20, 並行モードでも DROP をブロッキングな通常 DROP INDEX にしているので、並行実行の意図に反します:concurrently フラグが true のときは queryRunner.query の DROP を `DROP INDEX CONCURRENTLY "IDX_724b311e6f883751f261ebe378"` に変更し、CREATE と同様に並行オプションを使うようにしてください。また down 処理で既に用意されている mayConcurrently 変数を利用して DROP の呼び出しを切り替える(mayConcurrently ? 'DROP INDEX CONCURRENTLY ...' : 'DROP INDEX ...')ように修正してください。
|



What
Why
Additional info (optional)
Checklist
Summary by CodeRabbit
新機能
設定変更
多言語対応
CI/運用