Commit Graph
5168 Commits
Author SHA1 Message Date
Sam 06d833152f DEV: improve approach to removing JS from house ads (#37739)
We only really intended to remove JS so people use other patterns
this was not a security feature
2026-02-12 10:37:37 +11:00
Sam 63f7f10b85 DEV: fix house ad tests (#37737)
House ad tests were adding ids to house ads, this was being stripped
by our allow lister.

Not allowing ids is a feature, cause this protects people from footguns

Particularly if we allow it is much more likely they will end up having
dupe ids for ads
2026-02-12 09:39:59 +11:00
Sam f9fce4712b FEATURE: support target_user_ids for private messages (#37708)
Add `target_user_ids` as an alternative to `target_usernames` when
creating private messages via PostCreator and TopicCreator. The two
options are mutually exclusive and raise ArgumentError if both are
provided.

This avoids username-based lookups where the caller already has user
IDs, and prevents stale references when users rename after a pending
PM is created.

Key changes:
- PostCreator/TopicCreator: accept `target_user_ids`, validate
  mutual exclusivity with `target_usernames`, extract shared
  `add_users_from_scope` in TopicCreator
- Automation PendingPm: migrate from `sender`/`target_usernames`
  string columns to `sender_id`/`target_user_ids` integer columns
  with a backfill migration
- Scriptable::Utils.send_pm: resolve sender upfront, support
  integer sender param, store IDs in pending PMs
2026-02-12 09:21:40 +11:00
Sam 6cb3b14d7f FEATURE: enforce guardian permissions in AI tool runner (#37709)
Previously, tool runner actions (createTopic, createPost, editPost,
editTopic, sendChatMessage) used system_guardian or bypassed permission
checks, allowing AI tools to act beyond the resolved user's privileges.

This change:
- Adds `resolve_guardian` helper to pair user resolution with Guardian
- Replaces system_guardian with per-user guardian in all tool actions
- Adds explicit `can_create?`/`can_edit?`/`can_see?` checks before
  performing mutations
- Defaults to bot_user instead of system_user when no username given
- Removes redundant username requirement for sendChatMessage
- Adds comprehensive specs for guardian permission enforcement

This means you always want better alignment between bot user or username
that the custom tool uses. It makes it simpler to reason about
permissions
but also is a bit of a breaking change.
2026-02-12 08:48:19 +11:00
Sam 0f1c33fee6 FIX: Fix inconsistent route filtering in house ads initialization (#37700)
The original commit added route-based targeting for house ads, allowing
ads to be shown on specific Ember routes in addition to categories.
However, the filtering logic was implemented inconsistently - the
`chooseAdHtml()` method correctly filters by both categories and routes,
but the `didInsertElement()` method (which initializes random ad
positions) only filters by categories.
2026-02-12 08:44:52 +11:00
Sam af731daa52 FEATURE: Do not allow inline JS in house ads (#37701)
Admin ads the script here, but the intent was never to allow JS here, we
have other patterns for adding JS we should follow.
2026-02-12 07:48:01 +11:00
Rafael dos Santos Silva 882bdf8260 FEATURE: Add stalled_topic trigger to AI automation scripts (#37728)
- Add `stalled_topic` trigger to `llm_triage`, `llm_persona_triage`,
`ai_tool_action`, and `llm_tagger` automation scripts
- Each script resolves the first post from `context["topic"]` when
triggered by `stalled_topic` (which doesn't pass a post directly)
- Add tests for the new trigger in all 4 spec files
2026-02-11 15:19:54 -03:00
Joffrey JAFFEUX cda705f784 FIX: preview action crashes for anonymous users (#37719)
The `preview` action in `SharedAiConversationsController` was accessible
without authentication because it was missing from the `requires_login
only:` list, allowing anonymous users to hit the endpoint. However, it
would not create any security exploit due to guardians, and would just
generate an incorrect error later in the chain.

Note this commit also removes `update` from the list of required login
actions as it doesn’t exist anymore.
2026-02-11 18:16:06 +01:00
Régis Hanolandchapoi f068c56c1b DEV: Refactor Horizon topic cards to use listContext instead of route checks (#37355)
The Horizon theme's topic card system relied on inspecting
`router.currentRouteName` to decide when to apply card styling. This
was fragile — the theme was coupled to internal routing details — and
made it impossible to scope card behavior per-list (e.g. showing simple
cards for suggested topics but high-context cards on discovery).

This PR introduces a `listContext` string prop that flows through the
topic list component tree, replacing all route-name checks with explicit
context values.

## What changed

### Core: `listContext` prop threading

Every topic list call site now passes an explicit context string
(`"discovery"`, `"suggested"`, `"related"`, `"group-activity"`,
`"user-activity"`, `"messages"`, `"assigned"`). The prop flows from
the call site through `BasicTopicList` → `TopicList` → `Item` and into
all value transformers (`topic-list-class`, `topic-list-columns`,
`topic-list-item-class`, `topic-list-item-style`,
`topic-list-item-mobile-layout`) and the `topic-list-item-click`
behavior transformer.

This lets the theme decide card behavior based on semantic context
rather than route names, and means the theme no longer needs to
`container.lookup("service:router")`.

### Horizon initializer rewrite

- Replace `isHighContextRoute(router.currentRouteName)` with
  `isTopicCardContext(listContext)` checks using two declarative arrays:
  `TOPIC_CARD_CONTEXTS` and `SIMPLE_CARD_CONTEXTS`.
- Register a `topic-list-class` transformer that adds `--d-topic-cards`
  to topic lists in card contexts.
- Suggested/related lists always get the simple card layout, even when
  high-context mode is enabled.
- Preserve the `bulk-select` column in high-context layout (its item
  cell is hidden by existing CSS) so the header toggle icon renders.
- Set `header: HeaderTopicCell` on the high-context-card column so the
  bulk action buttons ("Select All" / "Clear All") appear in the header.

### CSS scoping and cleanup

- **Topic card styles** scoped to `.topic-list.--d-topic-cards` instead
  of applying globally to `.topic-list-body`, preventing style leakage
  into messages and other non-card lists.
- **Badge category colors** consolidated into CSS custom properties
  (`--badge-category-bg`, `--badge-category-text`) on
  `.badge-category__wrapper`, replacing 6 repeated
  `light-dark(oklch(...))` expressions across 3 files.
- **Messages page**: removed the 72-line `body.user-messages-page`
  override block. Messages get `listContext="messages"` which is not a
  card context, so they use default rendering. Desktop bulk-select and
  header overrides scoped with `body:not(.user-messages-page)`.
- **Bulk select in high-context cards**: fixed checkbox centering,
  selected-state background (removed override that reset it to
  `--secondary`), sticky header `z-index`, and `max-width: unset` on
  header cells at small viewports.
- **Removed** `color-exploration.scss` (unused sidebar color vars),
  `excerpt-expanded` grid area styles, hardcoded `#e45735` (now
  `var(--danger)`).
- **Renamed** `has-replies` → `--has-replies` for BEM consistency.

### Other fixes

- Extract `getTopicStatusBadge()` into shared utility, fixing
  inconsistency where `topic-status-column` only checked `topic.pinned`
  but the high-context card checked both `pinned` and `pinned_globally`.
- Fix `topic-list-item-mobile-layout` transformer to properly return
  `value` for non-card contexts (was returning `undefined`).
- Fix `hasReplies` to use `replyCount > 0` instead of `posts_count > 1`.
- Typo fix in `chat.scss`.

---------

Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
2026-02-11 16:13:58 +01:00
Joffrey JAFFEUX 7e980df9d4 FIX: Missing t.deleted_at IS NULL filter exposes deleted topics (#37718)
## Summary

The sentiment controller's `posts` action returns posts from
soft-deleted topics because the SQL query only checks `p.deleted_at IS
NULL` but not `t.deleted_at IS NULL`.

This is not a security exploit as SentimentController is a
StaffController.

## Source

- Patch Triage: https://patch.discourse.org/patch-triage/270
- Original Commit:
https://github.com/discourse/discourse/blob/main/plugins/discourse-ai/app/controllers/discourse_ai/sentiment/sentiment_controller.rb

---

🤖 Generated via [Patch Triage](https://patch.discourse.org/patch-triage)
2026-02-11 13:24:54 +01:00
Joffrey JAFFEUX b4d485fc1b FIX: Missing guardian.ensure_can_see!(post) in translate action (#37714)
Any authenticated user in the localization allowed group could trigger
translation of any post by ID, including posts in private messages,
restricted categories, whispers, and deleted posts.

This is not considered as SECURITY issue as the action will only start a
translation and return `{"success" => "OK"}`. You won't have access to
any inaccessible content.
2026-02-11 12:02:08 +01:00
Régis Hanol 9d4475ae5f FEATURE: Add FormKit TagChooser control (#37680)
The category tags page and webhooks form were using `@form.Container`
and `field.Custom` to wrap the select-kit TagChooser component. These
wrappers are simple visual containers that don't participate in
FormKit's field lifecycle — they silently ignore props like `@optional`
and don't support validation, error display, or consistent field
styling.

This commit introduces a proper `field.TagChooser` FormKit control that
wraps the select-kit TagChooser, mapping `@field.value` to `@tags` and
`@field.set` to `@onChange`. It follows the same pattern as the existing
`field.Icon` control.

Changes:
- New `fk/control/tag-chooser.gjs` with pass-through args for
TagChooser-specific options (`@everyTag`, `@excludeSynonyms`, etc.)
- Register the control in `fk/field.gjs` and add pass-through args to
`fk/control-wrapper.gjs`
- Convert `upsert-category/tags.gjs` allowed_tags from Container to
`field.TagChooser`
- Convert `webhooks-form.gjs` tag_names from `field.Custom` to
`field.TagChooser`
- Add `tag-chooser` support to the FormKit Ruby page object for system
specs
- Update simplified_category_creation_spec to use FormKit page objects
- Add integration tests and styleguide example
- Add webhook tag filter system spec

Ref - t/174117
2026-02-11 11:42:33 +01:00
Joffrey JAFFEUX 7bfec979fc DEV: loads post only once (#37692)
set_post before action is already setting the post so we don't have to
do it another time in not_accepted action.
2026-02-11 10:28:03 +01:00
Régis Hanol 39a426505d FEATURE: allow marking solutions in group messages (#37635)
PM support for the solved plugin was intentionally removed in
https://github.com/discourse/discourse-solved/pull/334, but multiple
communities rely on solved in group messages for private support
workflows (e.g. using Discourse as an internal ticket system).

Add a new `allow_solved_in_groups` site setting (group list, default
empty) that enables the solved UI for group messages belonging to the
configured groups. Regular 1-on-1 PMs remain excluded.

The implementation adds an early return in `allow_accepted_answers?` for
private messages that checks whether the topic's allowed groups
intersect with the configured setting. The category/tag logic is
extracted into `solved_enabled_for_category?` so callers without a topic
object (e.g. the category-change diff in plugin.rb) still work. All
existing PM exclusions (reports, badges, filters, directory columns) are
intentionally left unchanged.

https://meta.discourse.org/t/370496
2026-02-11 07:51:22 +01:00
Sam 2a3e6d9865 DEV: Remove redundant code and fix page object selectors (#37703)
minor cleanup
2026-02-11 16:34:41 +11:00
Sam 8cf9383fad DEV: gracefully handle missing parameters (#37705)
Avoid a 500 error when params are missing
2026-02-11 16:34:25 +11:00
Natalie Tay bac86460e1 DEV: Follow up spec fix (#37698)
There was a missing spec update to
https://github.com/discourse/discourse/pull/37055 ->
https://github.com/discourse/discourse/actions/runs/21890285424/job/63195732779
2026-02-11 11:51:51 +08:00
Natalie TayandKrzysztof Kotlarek 737577e8b4 DEV: Move canonical tag routes to /tag/slug/id keeping /tag/name support (#37055)
Switches tag URLs from name-based (`/tag/my-tag`) to slug+id-based
(`/tag/my-tag/123`), making tag references stable across renames and
enabling translated tag names.

  | Type | Before | After |
  |------|--------|-------|
  | Browser | `/tag/my-tag` | `/tag/my-tag/123` |
  | Browser | `/tag/my-tag/l/latest` | `/tag/my-tag/123/l/latest` |
  | API | `/tag/my-tag.json` | `/tag/123.json` |

- Old `/tag/:name` URLs still work
- Browser requests get 301 redirected to canonical URLs
- API (JSON) requests return data without redirect
-  Untagged (`/tag/none`) and intersection routes remain unchanged.


Depends on https://github.com/discourse/discourse/pull/36678

---------

Co-authored-by: Krzysztof Kotlarek <kotlarek.krzysztof@gmail.com>
2026-02-11 10:21:19 +08:00
79e3d8b003 FEATURE: add centralized AI secrets management (#37592)
Introduce an `AiSecret` model to allow admins to manage
API keys and secrets in a single place, shared across
LLMs and embedding definitions.

Previously each LLM and embedding stored its own api_key
directly. This change introduces a secrets vault so that
a single secret can be referenced by multiple models,
reducing duplication and making key rotation easier.

Key changes:
- New `ai_secrets` table, model, serializer, and CRUD
  controller with in-use protection on delete
- LlmModel and EmbeddingDefinition now accept an optional
  `ai_secret_id` foreign key as an alternative to inline
  `api_key`; validation ensures one or the other is set
- Provider params of type `:secret` (e.g. Bedrock
  `access_key_id`) resolve through AiSecret at runtime
- Admin UI: new Secrets nav tab with list/edit views,
  inline AiSecretSelector dropdown + quick-create modal
  on LLM and embedding editor forms
- Post-migration deduplicates existing api_key values
  into the new secrets table and back-fills foreign keys
- Fabricator and specs for model, controller, and
  usage-tracking logic

---------

Co-authored-by: awesomerobot <kris.aubuchon@discourse.org>
Co-authored-by: Keegan George <kgeorge13@gmail.com>
2026-02-11 10:09:52 +11:00
Joffrey JAFFEUX 8b7f48d24c DEV: parse json only once (#37689)
Before this commit we would parse the same JSON payload twice:
- in slack_payload_token_valid
- in interactive
2026-02-10 23:20:49 +01:00
Kris f585f0c73c UX: link chat settings in retention message (#37683)
When testing chat on a new site and seeing the retention warning... my
first thought was "what settings, where?"

This links the chat settings so admins can find them more easily. 

I also adjusted some styles for drawer mode

Before:
<img width="430" alt="image"
src="https://github.com/user-attachments/assets/ff37dc44-4d09-4c98-ba52-12864577af1e"
/>


After:

admin
<img width="420" alt="image"
src="https://github.com/user-attachments/assets/2efc4d7d-69bb-4985-a1de-48869f7bae0a"
/>

regular user (same) 
<img width="420" alt="image"
src="https://github.com/user-attachments/assets/3b9926a6-a127-4004-b2f9-3d91cef74a77"
/>
2026-02-10 16:15:34 -05:00
Discourse Translator Bot f65f4b7182 I18N: Update translations (#37671) 2026-02-10 14:50:31 +01:00
Joffrey JAFFEUX 1beffa4018 FIX: input was not defined (#37667) 2026-02-10 12:55:56 +01:00
Joffrey JAFFEUX a851595fc7 DEV: serializes a direct I18n string instead of error (#37664)
Currently `e.message` would return the same error, but returning an
exception message is brittle as it could change in the future and expose
data in the serializer we wouldn't want to expose. Having high control
on what we expose in the serializer is a more future proof pattern.
2026-02-10 11:10:21 +01:00
Joffrey JAFFEUX d78dd50b32 FIX: enforces params[:type] when listing invitees (#37663)
Ensures the `type` is valid. Before this commit, giving an invalid type
would just return the list of invitees who havent answered yet as it
would be computed to `nil`.
2026-02-10 10:52:54 +01:00
Joffrey JAFFEUX d2fef861e4 FIX: ensures we have valid event/invitee (#37662)
This codepath could hit a `NoMethodError` (500 error) in
`InviteesController#update` and `#destroy` when invitee or event is not
found, due to calling methods on `nil`.
2026-02-10 10:19:36 +01:00
Joffrey JAFFEUX 0c8f52f420 FIX: CreateThread allows thread creation in closed/read-only/archived channels (#37661)
## Summary

`Chat::CreateThread` allows thread creation in closed, read_only, and
archived channels because it only checks `can_preview_chat_channel?`
(view access) without verifying the channel's status permits content
creation.

## Source

- Patch Triage: https://patch.discourse.org/patch-triage/237
- Original Commit:
https://github.com/discourse/discourse/blob/main/plugins/chat/app/controllers/chat/api/channel_threads_controller.rb

---

🤖 Generated via [Patch Triage](https://patch.discourse.org/patch-triage)
2026-02-10 10:14:26 +01:00
Sam 70264aa43b DEV: Require login on state changing routes (#37651)
No need to to hit the controller actions here.
2026-02-10 18:18:02 +11:00
Sam 8de7122fb1 DEV: Remove dead CSS for .btn.--active after class rename (#37654) 2026-02-10 11:41:57 +08:00
David McClure 5653700c30 DEV: Update chat usage strings (#37640)
DM Messages feels redundant and was confusing to translators.

@discourse-translator-bot keep_translations_and_approvals
2026-02-10 14:27:43 +11:00
Sam 21428487be DEV: Unvalidated llm_id Parameter in test Action Allows Use of Any LLM Model (#37652)
Unvalidated `llm_id` parameter in `AiSpamController#test` allows use of
any LLM model, including non-existent ones, causing unhandled
`ActiveRecord::RecordNotFound` errors.
2026-02-10 14:27:03 +11:00
David Taylor 0d26f3ac0a DEV: Prevent <style> breakout in AI ArtifactController (#37637)
This is not a security issue, because artifacts are allowed to include
arbitrary javascript anyway. Plus, the injection takes place inside a
sandboxed iframe.

Internal `patch-triage/263`
2026-02-10 12:17:06 +11:00
Sam d76702ec18 DEV: Remove old ignore columns (#37558) 2026-02-10 09:40:07 +11:00
Kris 5b42280cc1 UX: update default LLM selector (#37616)
This updates the default LLM selector so that we can have a "none"
option. I've also made this auto-save on change with a toast rather than
using the site setting component.

If you only had one LLM configured and set as the default, and then
wanted to delete that LLM, you'd have to know that this default LLM
setting is duplicated in site settings and go reset it there (because
were hiding the reset button in this context).

Now you can just select the none option from the dropdown. 

Before:
<img width="1000" alt="image"
src="https://github.com/user-attachments/assets/984ad1de-da88-47ab-9cad-5e7bcddf8c36"
/>



After:

None 
<img width="1000" alt="image"
src="https://github.com/user-attachments/assets/33ff92b2-c108-4d00-833e-540bc24a80de"
/>

Saving
<img width="1000" alt="image"
src="https://github.com/user-attachments/assets/8213027e-0777-433c-856c-a49b26afe9b7"
/>

Saved
<img width="1000" alt="image"
src="https://github.com/user-attachments/assets/d1dc8db9-ae1c-4564-993b-d32adb807b62"
/>

toast on success
<img width="400" alt="image"
src="https://github.com/user-attachments/assets/3b3fc151-1204-4f7d-b86d-300a425c5cf6"
/>
2026-02-09 17:30:20 -05:00
Régis Hanol 318eb49a30 FIX: Skip GitHub linkback when topic is already linked (#37633)
The GitHub linkback feature posts comments on PRs/issues/commits when
they are mentioned in Discourse posts. However, it never checked whether
the GitHub PR/issue/commit already contained a link back to the same
Discourse topic (in its description or existing comments). This led to
redundant linkback comments when a PR already referenced the topic.

Before posting a linkback comment, we now fetch the PR/issue body and
existing comments (or commit comments) from the GitHub API and check
whether any of them already contain a URL pointing to the same Discourse
topic. URL matching uses Discourse.route_for — the same mechanism used
by the oneboxer, search, and TopicLink — to reliably recognize all topic
URL formats (/t/slug/id, /t/id, /t/id/post_number, etc.).

On any GitHub API error, the check fails open (still posts the linkback)
to preserve the pre-existing behavior — and because if the GET fails,
the subsequent POST will likely fail too.
2026-02-09 14:31:27 +01:00
Renato Atilio 936cf2ebc3 DEV: event node view link validation from getLinkify (#37607)
Changes the event node view link validation, which is only used to
display a "visit link" button on the node view, to use the logic from
markdown-it's linkify.

To achieve this, we expose `pluginParams` (the same that we can access
from input rules, ProseMirror plugins, keymap, etc) to the
`GlimmerNodeView`.

It also moves the event removal regex to an existing helper file, and
makes it support content between the `[event]` open and close tags.
2026-02-09 09:44:35 -03:00
Renato Atilio f92be60724 UX: avoid destructive leave group DM on chat drawer (#37613)
We have recently adjusted the sidebar action of leaving a group DM to
avoid the destructive leave action, which removes the membership
entirely, so the user can't find and reopen that same group DM again.

This PR makes sure the DM list's X to close a group DM does the same.

<img width="435" height="75" alt="image"
src="https://github.com/user-attachments/assets/8138abd6-0972-4117-ae36-b78d66e7ccbb"
/>
2026-02-09 09:44:18 -03:00
Renato Atilio fcd319b8d2 DEV: Remove unused avatar helper parameters in poll voters (#37620)
The `avatar` helper from `bound-avatar-template` do not expect
`usernamePath`, `namePath`, or `title`, so they're currently being
ignored.

This PR removes these unnecessary arguments and adds a `title` to the
`a`.
2026-02-09 09:43:01 -03:00
David Battersby d32439da43 FEATURE: confirm solved topic change to non solved category (#37601)
This change adds a confirmation modal when moving the topic from a
solved category to a non-solved category when the topic has an accepted
solution. If the admin continues then the solved answer data is removed
from the answer post and the post stream is refreshed to remove the
solved data from the UI (ie. checkbox to mark as solved).

The admin also has the option of hiding the modal in future by selecting
the checkbox when confirming, which uses local storage to record their
preference.

How the modal looks:

<img width="550" alt="Confirmation modal when moving from solved
category"
src="https://github.com/user-attachments/assets/a016f0b8-e7a2-4adb-8ee2-1a2efec230ba"
/>

Internal ref - /t/173671
2026-02-09 13:47:17 +04:00
Discourse Translator Bot 03c45bbdab I18N: Update translations (#37631) 2026-02-09 09:34:22 +01:00
Jarek Radosz 1226e226b0 DEV: ChatThreadTitler persona can be missing in specs (#37608)
A followup to 6c7bda7cea
2026-02-06 18:41:05 +01:00
Renato Atilio 6a8701c677 DEV: remove invalid href attribute from p element (#37600)
Removes `href` attribute from a `<p>` element – the same `href` is
already present on a parent `<a>`.
2026-02-06 12:12:13 -03:00
Discourse Translator Bot 58e7b15d51 I18N: Update translations (#37596) 2026-02-06 16:08:58 +01:00
David Battersby 2b24fc91c1 FIX: prevent chat reactions from non channel members (#37078)
This is a follow up to the removal of chat action buttons for non
channel members in #37060

Previously we allowed non channel members to react to posts, now the
user can still remove reactions they left in the past (if they were once
a channel member and then left), but attempting to add new reactions via
the API or by clicking other user reactions to increment them would
result in an error message stating that you need to join the channel.

From the API request to add a reaction they would get a 403 response
code with a message `You must be a member of this channel`.

If clicking an existing reaction from the UI they will get an error
dialog:

<img width="550" alt="When non members click existing reactions"
src="https://github.com/user-attachments/assets/57b7a205-e5b7-426d-a64d-19dd04803686"
/>


Internal ref - /t/160400
2026-02-06 10:43:00 +04:00
Jarek Radosz 6c7bda7cea DEV: Fix persona name collision in chat_thread_titler_spec (#37584) 2026-02-05 22:44:47 +01:00
Kris ae2ab5a2d0 FIX: more specific name for rewind blink animation (#37576)
this was leaking into another animation and causing interesting results
😆

<img width="350" alt="image"
src="https://github.com/user-attachments/assets/a4738717-37c2-4f9f-b0dc-d8937d00037e"
/>
2026-02-05 14:00:15 -05:00
Discourse Translator Bot e2dc31b2cf I18N: Update translations (#37563) 2026-02-05 19:29:53 +01:00
Sam 6095ed3d0c DEV: Improved error message when not allowed to edit posts with AI tool (#37547)
Previously the PostRevisor handled the check, but this change both adds
a test and ensures the consumer gets a nicer error message
2026-02-05 18:04:12 +11:00
Sam 7fa0e65f5a FEATURE: add persona support for chat thread title generation (#37557)
Introduce a dedicated ChatThreadTitler persona to power automatic chat
thread title generation, replacing the hardcoded prompt approach.
2026-02-05 18:03:44 +11:00
Sam 775cb5f236 FIX: null dereference in GithubDiff for deleted forks/users (#37548)
Allows llm to get richer information in cases where a diff is on a
deleted fork or for a deleted user
2026-02-05 18:00:33 +11:00