Skip to content

Add custom background task API (defineTask / enqueueTask)#803

Open
2chanhaeng wants to merge 25 commits into
fedify-dev:feat/custom-workerfrom
2chanhaeng:main
Open

Add custom background task API (defineTask / enqueueTask)#803
2chanhaeng wants to merge 25 commits into
fedify-dev:feat/custom-workerfrom
2chanhaeng:main

Conversation

@2chanhaeng

@2chanhaeng 2chanhaeng commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Resolves #797, the first sub-issue of #206.

Summary

Fedify already runs a background worker that processes outgoing and incoming activities through a message queue (enqueue work, return the HTTP response immediately, process the payload on a separate worker later). This PR generalizes that fire-and-then-process pattern to arbitrary application-defined jobs—digest mailers, timeline rebuilds, link-preview fetches, periodic cleanup, and so on—so applications can reuse Fedify's worker instead of standing up a separate task system.

This delivers the end-to-end core only: define a task, enqueue it from a Context, run it on a worker, retry on failure. Deduplication (#798) and task-specific telemetry attributes (#799) are deliberately left to their own sub-issues to keep this PR reviewable.

Public API

Registration

defineTask() lands on a new TaskRegistry<TContextData> interface that Federatable<TContextData> now extends, so both Federation and FederationBuilder inherit it. A task always carries a Standard Schema (Zod, Valibot, ArkType, …); the payload type is inferred from the schema via StandardSchemaV1.InferOutput, so there is no explicit-TData escape hatch and no unknown fallback. The returned TaskDefinition<TContextData, TData> is an opaque handle carrying name and schema plus a phantom TContextData marker—no runtime methods.

const sendDigest = federation.defineTask("sendDigest", {
  schema: z.object({ userId: z.string(), since: z.date() }),
  handler: async (ctx, data) => {
    // data is typed as { userId: string; since: Date }
  },
});

Defining the same name twice throws a TypeError, and build() clones the registry so two builds stay isolated.

Dispatch

Two methods land on Context<TContextData> alongside sendActivity / routeActivity:

enqueueTask<TData>(task, data, options?): Promise<void>;
enqueueTaskMany<TData>(task, payloads, options?): Promise<void>;

interface TaskEnqueueOptions {
  readonly delay?: Temporal.DurationLike;
  readonly orderingKey?: string;
}

enqueueTask validates data against the task's schema (fail fast), encodes it, and stores the resulting string in a TaskMessage before handing it to the queue. enqueueTaskMany resolves the queue once and uses MessageQueue.enqueueMany when available, falling back to parallel single enqueues otherwise. Both go through the private #enqueueTasks helper, which encodes payloads in parallel (a rejected encode rejects the whole batch before anything is enqueued, preserving fail-fast).

Payload serialization

Serialization is owned by Fedify, not the caller. Payloads are encoded with devalue, so Date, Map, Set, URL, RegExp, bigint, typed arrays, circular references, and repeated references all round-trip—not just JSON-safe values. Activity Vocabulary objects (Note, Create, Person, …) keep their state in private fields and cannot be serialized structurally, so devalue's custom-type hook bridges each one through its expanded JSON-LD document (no @context, so decoding never touches the network). Encoding and decoding are therefore asynchronous. In the schema, a vocabulary object is described as an opaque instanceof leaf (z.instanceof(Create) / v.instance(Create)), keeping the envelope schema small.

Queue routing and worker

  • New task?: MessageQueue slot in FederationQueueOptions.
  • A task's queue resolves as: per-task queue (passed to defineTask) → FederationQueueOptions.taskoutboxQueue fallback → throw. There is no per-call queue override; a task's destination is declared once.
  • FederationOptions.taskQueueResolution?: "fallback" | "strict" (default "fallback") opts out of the outboxQueue fallback for deployments that isolate task work.
  • Federation.startQueue(ctxData, { queue: "task" }) starts a dedicated task-only worker (the selector now accepts "inbox" | "outbox" | "fanout" | "task").

On dispatch, the worker looks up the handler by taskName (missing → log + drop), decodes and re-validates data at the dequeue boundary as drift protection (failure → log + drop, no retry), then invokes the handler. On throw it calls onError, then either rethrows (nativeRetrial queues, so the backend owns retries) or applies the retry policy (per-task → FederationOptions.taskRetryPolicy → exponential backoff) and re-enqueues with attempt + 1.

TaskMessage keeps every field a string/number/plain record (the payload is a pre-encoded string, not unknown) so the message survives both JSON serialization and structured clone across every queue backend. Message is documented as opaque, so the new variant is non-breaking.

Forward compatibility

The TaskRegistry seam exists so Approach 2 from #206 (a separated Worker class) can land without call-site churn: Federatable stops extending TaskRegistry, a Worker-like class implements it instead, TaskMessage.taskName stays a location-agnostic string, and the task queue slot becomes WorkerOptions.queue verbatim. A type-level test asserts Federatable is assignable to TaskRegistry<TContextData>.

Acceptance criteria

  • ctx.enqueueTask(task, data) round-trips to a typed handler; a wrong-shaped payload is a compile error.
  • schema (required) infers TData; an invalid decoded payload is dropped without retry.
  • A payload carrying a vocab object (Note/Create) alongside Date/Map round-trips faithfully through the devalue codec.
  • A decode failure on a malformed wire string is dropped without retry.
  • Handler throw → retry with backoff; a nativeRetrial queue rethrows without re-enqueue; per-task retryPolicy overrides the federation default.
  • Unknown taskName → drop + warning.
  • Queue routing precedence and taskQueueResolution: "strict" behave as specified; startQueue({ queue: "task" }) starts only the task worker.
  • enqueueTaskMany uses enqueueMany when available, else parallel enqueue.
  • defineTask with a duplicate name throws; builder clone isolation holds across two build() calls.
  • Type-level forward-compat guard: Federatable is assignable to TaskRegistry<TContextData>.
  • docs/manual/tasks.md covers defining, payload handling, dispatching, retry/error, and queue isolation; CHANGES.md updated; new public types carry @since 2.3.0 JSDoc.

Notes for reviewers

  • The payload codec is committed in two forms on purpose, and only one is wired in. TaskCodec class is chosen.
  • A tooling change rides along in a second commit. "Restructure mise install tasks and update docs" splits install into install:deno / install:pnpm (with codegen as an explicit dependency) and repoints test:deno at install:deno instead of prepare, since Deno runs the sources directly and does not need the build step; it also updates AGENTS.md. It is unrelated to Custom background tasks: core defineTask / enqueueTask API #797 and can be split into its own PR if preferred.

Generalize Fedify's enqueue-and-process-later pattern, previously
limited to outgoing activity delivery, to arbitrary application-defined
background jobs.  `Federation` and `FederationBuilder` gain
`defineTask()` (via the new `TaskRegistry` interface), and `Context`
gains `enqueueTask()`/`enqueueTaskMany()`.  Each task carries a Standard
Schema that infers the payload type and validates it both at enqueue
time and at dequeue time, guarding against schema drift across
deployments.

Payloads are serialized with devalue so that `Date`, `Map`, `Set`,
`URL`, `bigint`, circular references, and Activity Vocabulary objects
round-trip faithfully across every message queue backend.  Failed
handlers retry with exponential backoff by default, configurable per
task or federation-wide, and tasks can be isolated onto a dedicated
queue or fall back to the outbox queue.

The payload codec is implemented twice on purpose: `codec.ts` as a
class (`TaskCodec`) and `codec-fn.ts` as standalone utility functions,
each with its own tests.  Only the class is wired into the runtime; the
functional variant is kept temporarily so the team can compare the two
styles and decide which reads better before one is removed.

fedify-dev#206
fedify-dev#797

Assisted-by: Claude Code:claude-opus-4-8
Split the monolithic `install` task into `install:deno` and
`install:pnpm`, with `codegen` as an explicit dependency, so each
runtime's setup can be run on its own.  `test:deno` now depends on
`install:deno` instead of `prepare`, since Deno runs the TypeScript
sources directly and does not need the build step.

Update AGENTS.md to match: document `mise run prepare`/`prepare-each`
for building, `check-each` and `test-each` for scoping work to specific
packages, and add a section directing agents to consult `mise tasks`.

Assisted-by: Claude Code:claude-opus-4-8
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 155071a0-e7a2-4525-851e-bf208e9ae5d9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a custom background task API to Fedify, allowing applications to define, enqueue, and process arbitrary background jobs. Payloads are validated using Standard Schema and serialized with devalue to support complex types, including Activity Vocabulary objects. The review feedback highlights several critical improvements: refining isPlainObject to support objects with null prototypes, implementing a recursion depth limit in reviveVocab to prevent stack overflow vulnerabilities, replacing Array.fromAsync with Promise.all for Node.js 18/20 compatibility, and defensively handling potential parsing errors when reading message.started with Temporal.Instant.from.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/fedify/src/federation/tasks/codec-fn.ts Outdated
Comment thread packages/fedify/src/federation/tasks/codec-fn.ts Outdated
Comment thread packages/fedify/src/federation/middleware.ts
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.03546% with 21 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/testing/src/mock.ts 80.85% 6 Missing and 3 partials ⚠️
packages/testing/src/context.ts 25.00% 6 Missing ⚠️
packages/fedify/src/testing/context.ts 50.00% 4 Missing ⚠️
packages/fedify/src/federation/middleware.ts 99.55% 0 Missing and 1 partial ⚠️
packages/fedify/src/federation/tasks/codec.ts 99.09% 0 Missing and 1 partial ⚠️
Files with missing lines Coverage Δ
packages/fedify/src/federation/builder.ts 61.64% <100.00%> (+0.95%) ⬆️
packages/fedify/src/federation/mod.ts 100.00% <ø> (ø)
packages/fedify/src/federation/tasks/mod.ts 100.00% <100.00%> (ø)
packages/fedify/src/federation/middleware.ts 90.93% <99.55%> (-4.70%) ⬇️
packages/fedify/src/federation/tasks/codec.ts 99.09% <99.09%> (ø)
packages/fedify/src/testing/context.ts 73.41% <50.00%> (-1.25%) ⬇️
packages/testing/src/context.ts 51.83% <25.00%> (-1.18%) ⬇️
packages/testing/src/mock.ts 74.54% <80.85%> (+0.32%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The `install:deno` task runs `scripts/install.ts`, which `deno cache`s
each workspace member's export entry points; those include the generated
`packages/vocab/src/vocab.ts`.  `install:pnpm` likewise expects the
generated sources to be present.  Both therefore require `codegen` to
have run first.

Previously `codegen` sat alongside `install:deno` and `install:pnpm` in
the `install` task's `depends` list, which `mise` runs in parallel, so
the cache step could start before `vocab.ts` was generated.  Move
`codegen` into each subtask's own `depends` so it is ordered before them;
`mise` dedupes the shared dependency to a single run.  As a result
`install:deno` and `install:pnpm` are now correct when invoked on their
own, not only as part of `install`.

Also correct the `install:pnpm` description, which said "for Deno".

Assisted-by: Claude Code:claude-opus-4-8
`isPlainObject` in the task codec only accepted objects whose
prototype is exactly `Object.prototype`, so an object made with
`Object.create(null)` was treated as a non-plain leaf.  Any vocab
object nested inside such an object was therefore left as its parked
holder instead of being revived, even though devalue round-trips
null-prototype objects without throwing.

Accept a `null` prototype as well, and add a regression test that
round-trips a vocab object nested in an `Object.create(null)` object.

fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-opus-4-8
When a custom task handler throws and the queue does not own retries,
the error path computes the elapsed time from `message.started` to
feed the retry policy.  `message.started` is normally a valid ISO
instant set at enqueue time, but a corrupted or drifted queue could
hand back an invalid string, in which case `Temporal.Instant.from()`
threw out of the error-handling block.  That masked the original
handler error and aborted the retry, silently dropping the task.

Wrap the parse in a try-catch, fall back to a zero elapsed time, and
log the offending value.  A regression test drives a message with a
malformed `started` through a throwing handler and asserts the retry
is still enqueued.

fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-opus-4-8
@2chanhaeng

Copy link
Copy Markdown
Contributor Author

@claude review
@coderabbitai review
@codex review
@gemini-code-assist review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the list of changed files in the PR
gh pr view 803 --repo fedify-dev/fedify --json files --jq '.files[].path' | head -60

Length of output: 1034


🏁 Script executed:

#!/bin/bash
# Get overall PR diff stats
gh pr diff 803 --repo fedify-dev/fedify --name-only 2>/dev/null | head -60

Length of output: 1010


🏁 Script executed:

#!/bin/bash
# Get the PR diff
gh pr diff 803 --repo fedify-dev/fedify 2>/dev/null | head -3000

Length of output: 106514


🏁 Script executed:

#!/bin/bash
gh pr diff 803 --repo fedify-dev/fedify 2>/dev/null | tail -n +3000 | head -2000

Length of output: 4404


🏁 Script executed:

#!/bin/bash
# Check the codec.ts imports from codec-fn.ts
gh pr diff 803 --repo fedify-dev/fedify 2>/dev/null | grep -A5 "import.*codec-fn"

Length of output: 383


🏁 Script executed:

#!/bin/bash
# Check what's exported from mod.ts tasks barrel
cat packages/fedify/src/federation/tasks/mod.ts 2>/dev/null || echo "File not found in working tree"
# Also look at what's exported from federation/mod.ts
grep -A20 "tasks/mod" packages/fedify/src/federation/mod.ts 2>/dev/null

Length of output: 917


🏁 Script executed:

#!/bin/bash
# Check processQueuedTask visibility
grep -n "processQueuedTask" packages/fedify/src/federation/middleware.ts 2>/dev/null | head -20
# Check TaskCodec `#seen` usage
grep -n "seen" packages/fedify/src/federation/tasks/codec.ts 2>/dev/null

Length of output: 950


🏁 Script executed:

#!/bin/bash
# Verify clampNegativeDelay exists
grep -n "clampNegativeDelay" packages/fedify/src/federation/middleware.ts | head -10

# Verify isObjectPrototype difference between two codecs
grep -n "isObjectPrototype\|isPlainObject\|getPrototypeOf" packages/fedify/src/federation/tasks/codec.ts packages/fedify/src/federation/tasks/codec-fn.ts 2>/dev/null

# Check devalue upgrade effect on SvelteKit packages
grep -B5 -A5 "devalue.*5.1.1\|devalue.*5.8.1" pnpm-lock.yaml | grep -v "^--$" | head -40

Length of output: 3133



@2chanhaeng: Great PR — the end-to-end design is sound, the test suite is thorough, and the docs page covers all the major surface area. Below is a full review with the codec-style decision called out first (as requested), followed by the other issues I found.


Codec style: keep codec.ts (class-based)

Recommendation: delete codec-fn.ts and keep codec.ts.

Reasons:

  1. codec.ts is already wired to the runtime — no code change needed to activate it.

  2. codec.ts handles null-prototype objects; codec-fn.ts does not. The isPlainObject implementations diverge:

    File Condition
    codec.ts proto === null || proto === Object.prototype
    codec-fn.ts proto === Object.prototype only

    Objects created with Object.create(null) are opaque records in codec-fn — any vocab value nested inside them is never revived. Only codec.test.ts covers this case ("revives a vocab object nested in a null-prototype object"); codec-fn.test.ts has no equivalent test.

  3. codec.ts carries its loader state on the instance, which is a natural fit for a context-bound component and easier to extend later.


Issues to resolve before the codec-fn deletion

1. Cross-dependency: codec.ts imports TaskCodecLoaders from codec-fn.ts

// codec.ts line 3
import type { TaskCodecLoaders } from "./codec-fn.ts";

Deleting codec-fn.ts will immediately break codec.ts. Before the deletion, move TaskCodecLoaders (and its VocabKind/VocabWire if needed) to task.ts or a new types.ts, then update both codec files to import from there.

2. tasks/mod.ts re-exports all of codec-fn.ts

// tasks/mod.ts
export * from "./codec-fn.ts";   // ← becomes an error once the file is deleted
export { default as TaskCodec } from "./codec.ts";
export * from "./task.ts";

After deleting codec-fn.ts, this line must be removed. Also verify that nothing in the internal consumers (middleware.ts, builder.ts, etc.) inadvertently relies on the serializeTaskData / deserializeTaskData / validateTaskData names that currently fall through via this wildcard.


Other issues

3. resolveTaskQueue silent drop under strict mode on handler failure

In #listenTaskMessage, when a handler throws:

const queue = this.resolveTaskQueue(def.name);
if (queue?.nativeRetrial) throw error;
// …
if (delay != null && queue != null) {
  // re-enqueue
} else {
  logger.error("Custom task … giving up …");
}

Under taskQueueResolution: "strict", if a task has no queue configured, resolveTaskQueue returns undefined. Enqueuing that task already throws at call-time, so this path should be unreachable in practice. However, a processQueuedTask call that bypasses enqueueTask (e.g., direct queue injection or a test) would silently drop the task on retry. Consider adding a logger.warn or an assertion in the queue == null branch so the drop is always observable.

4. #enqueueSingular is an arrow-function property, not a prototype method

`#enqueueSingular` = <TData>() => async (data: TData): Promise<TaskMessage> => {  };

Arrow properties are allocated per instance. For a long-lived context object this is negligible, but it's inconsistent with all other methods on ContextImpl. Converting it to a regular private method — or inlining the curry into #enqueueTasks — would be idiomatic.

5. Two @ts-ignore in codec.ts's #classRevivers

// `@ts-ignore` tsc faults
const out: Revived = await init(node);
// …
// `@ts-ignore` tsc faults
await set(this.#revive(seen), node, out);

These suppress real type errors in the generic dispatch through ClassReviver. They're acceptable as a short-term workaround, but leaving them in a permanent implementation is a smell. A narrower ClassReviver union or a discriminated-union approach would let the compiler check these calls properly.

6. __contextData phantom field leaks into the public TaskDefinition interface

export interface TaskDefinition<TContextData, TData> {
  readonly name: string;
  readonly schema: StandardSchemaV1<unknown, TData>;
  /** `@internal` Phantom marker binding the handle to its federation. */
  readonly __contextData?: TContextData;
}

The @internal comment is accurate, but the field still appears in user-facing docs and IDEs. Consider moving the phantom into the internal TaskDefinitionInternal shape instead, or using a declare symbol key that is invisible at the JS level.

7. processQueuedTask is public on FederationImpl

The test suite calls federation.processQueuedTask(…) directly, which is why it must be public. If it's not meant to be part of the external Federation interface, a /** @internal */ JSDoc tag (or a leading _ convention used elsewhere in the codebase) would signal that to callers.


Minor / doc nits

  • docs/manual/tasks.md: The "Limitations" section is a good addition. Consider linking each listed limitation to its specific tracking sub-issue (e.g., #798 for deduplication, #799 for telemetry) so users can subscribe to individual items.
  • CHANGES.md: The [Add custom background task API (defineTask / enqueueTask) #803] self-reference in the changelog entry is unusual — typically only the resolving issues are cited. Worth double-checking the project's convention.
  • The devalue bump from 5.7.1 → 5.8.1 also resolved the SvelteKit devalue dependency (5.1.1 → 5.8.1) via pnpm deduplication. This is harmless since SvelteKit's constraint is ^5.x, but worth a quick sanity-check that the SvelteKit docs build still works.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a custom background task API to Fedify, allowing applications to define and enqueue arbitrary background jobs with type-safe payloads validated via Standard Schema and serialized using devalue. Feedback on the implementation highlights a potential Denial of Service (DoS) vulnerability via stack overflow in the recursive payload revival, recommending a recursion depth limit and optimized reviver lookup. Additionally, suggestions were made to implement lazy cloning to avoid unnecessary container creation, and to fix a bug in codec-fn.ts where null-prototype objects are not recognized as plain objects, preventing nested vocabulary objects from being revived.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/fedify/src/federation/tasks/codec.ts Outdated
Comment thread packages/fedify/src/federation/tasks/codec.ts Outdated
Comment thread packages/fedify/src/federation/tasks/codec-fn.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2a2d4c5c6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/fedify/src/federation/middleware.ts
Comment thread packages/fedify/src/federation/builder.ts Outdated
`FederationBuilderImpl.taskDefinitions` was a plain object, so the
duplicate check `name in this.taskDefinitions` and the lookups
`this.taskDefinitions[taskName]` consulted the prototype chain.  Task
names are arbitrary user-supplied strings, so a name such as
"constructor", "toString", or "__proto__" was wrongly reported as
already defined and resolved to an inherited method on lookup.

Switch the registry to a `Map`, which is immune to prototype keys by
construction and avoids the clone footgun where a later spread or
`Object.assign` would silently reintroduce the prototype.  Sibling
registries stay plain objects since they are keyed by controlled
values (type-id URLs).  Add a regression test covering names that
collide with `Object.prototype`.

fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-opus-4-8
`#revive` mapped every node through all five class revivers, allocating
five promises per node and resolving them with `Array.fromAsync` before
picking the first truthy result.  The class filters are mutually
exclusive, so it now finds the single matching reviver and runs only
that one, cutting the per-node work to a single promise.

This keeps the existing behaviour (cycles, repeated references, and
Map/Set/Array/plain-object/null-prototype containers all still
round-trip, as the codec tests assert) and folds the rationale for two
declined suggestions into a comment: the walked tree is devalue's
throwaway parse output, so there is no external identity to preserve and
nothing to clone lazily; and a recursion-depth cap is moot because this
pass recurses with `await` (unwinding the stack each level) while
devalue's own recursive `stringify`/`parse` is the binding limit on
nesting and would overflow first.

fedify-dev#803 (comment)
fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-opus-4-8
A task may route to its own queue via `defineTask(name, { queue })`, and
`resolveTaskQueue()` enqueues its messages there, but
`_startQueueInternal()` only listened on the four federation-wide queues
(inbox, outbox, fanout, task).  A task queue that was none of those got
no worker, so its messages were never processed even while
`startQueue()` was running.

Collect the distinct dedicated queue instances from the task registry
and start a worker for each, treating them as part of the "task"
selector.  Dedupe against the standard queues and against task queues
already started on an earlier call so no instance is listened on twice,
and let a deployment whose only queues are per-task ones still start:
the early return no longer bails out when a dedicated task queue exists.

fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-opus-4-8
@dahlia

dahlia commented Jun 11, 2026

Copy link
Copy Markdown
Member

I agree on @coderabbitai's this comment:

Codec style: keep codec.ts (class-based)

Recommendation: delete codec-fn.ts and keep codec.ts.

Reasons:

  1. codec.ts is already wired to the runtime — no code change needed to activate it.

  2. codec.ts handles null-prototype objects; codec-fn.ts does not. The isPlainObject implementations diverge:

    File Condition
    codec.ts proto === null || proto === Object.prototype
    codec-fn.ts proto === Object.prototype only

    Objects created with Object.create(null) are opaque records in codec-fn — any vocab value nested inside them is never revived. Only codec.test.ts covers this case ("revives a vocab object nested in a null-prototype object"); codec-fn.test.ts has no equivalent test.

  3. codec.ts carries its loader state on the instance, which is a natural fit for a context-bound component and easier to extend later.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@dahlia Thanks for confirming! Before codec-fn.ts can be safely deleted, there are two small housekeeping steps to take care of first:

  1. Move TaskCodecLoaders (and any related types) out of codec-fn.tscodec.ts currently imports the type from there:

    import type { TaskCodecLoaders } from "./codec-fn.ts";

    Relocate TaskCodecLoaders (and VocabKind/VocabWire if they live alongside it) to task.ts or a new types.ts, then update codec.ts to import from the new location.

  2. Remove the wildcard re-export from tasks/mod.ts:

    export * from "./codec-fn.ts";   // ← must be removed

    Also double-check that nothing in middleware.ts, builder.ts, or other internal consumers references the functional names (serializeTaskData / deserializeTaskData / validateTaskData) that currently fall through via this wildcard.

Once those two changes are in place, codec-fn.ts and its test file can be deleted cleanly, and the rest of the review items above remain open.

@dahlia, the maintainer picks `TaskCodec` because it carries the loader state on the instance at [a comment](fedify-dev#803 (comment)). Therefore remove the *codec-fn.ts*.

`TaskCodecLoaders` moved to *codec.ts* because `TaskCodec` use it.
The task payload schema validates on both sides of the queue: at
enqueue time and again at dequeue time.  The wire therefore carries
the validated *output*, which the same schema must re-accept as input,
so transforming schemas (e.g., Zod's .transform()) whose output
differs in shape from their input cannot round-trip.  This constraint
was neither documented nor tested; state it in the manual and the
schema option's JSDoc, and pin it with a regression test.

fedify-dev#803

Assisted-by: Claude Code:claude-fable-5
resolveTaskQueue() returns the fallback queue even for a task name
with no registered definition, so enqueuing a handle created by a
different federation instance silently succeeded and the worker later
dropped the message with only a warning.  The task API's contract is
to fail fast at the enqueue call site (it already validates the
payload there), so check the registry before resolving a queue and
throw a TypeError instead.

fedify-dev#803

Assisted-by: Claude Code:claude-fable-5
Small follow-ups from review:

 -  Document that tasks must be defined before startQueue() (or the
    first request); workers for dedicated per-task queues are only
    registered when the queue machinery starts, so a queue defined
    later never gets a worker.
 -  Return early from the enqueue path when no payloads are given,
    instead of reaching enqueueMany()/Promise.all with an empty
    batch, whose backend behavior is undefined.
 -  Rename #enqueueSingular to #encodeTaskMessage; it encodes and
    builds a TaskMessage but does not enqueue anything.
 -  Fix a comment typo in the codec.

fedify-dev#803

Assisted-by: Claude Code:claude-fable-5
@2chanhaeng

Copy link
Copy Markdown
Contributor Author

I agree on @coderabbitai's this comment:

Addressed in e8bd70d! Deleted codec-fn.ts and its test file, moved the TaskCodecLoaders type into codec.ts (its only consumer), and removed the wildcard re-export from tasks/mod.ts.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the changes in commit e8bd70dfd
gh api repos/fedify-dev/fedify/commits/e8bd70dfd --jq '.commit.message'
echo "---"
gh api repos/fedify-dev/fedify/commits/e8bd70dfd --jq '.files[].filename'

Length of output: 758



@2chanhaeng: Confirmed via commit e8bd70dfd — all three housekeeping steps are complete:

  • codec-fn.ts and codec-fn.test.ts deleted ✅
  • TaskCodecLoaders moved into codec.ts
  • Wildcard re-export removed from tasks/mod.ts

The remaining open items from the review are issues 3–7 (silent drop under strict mode, #enqueueSingular arrow-function property, @ts-ignore in #classRevivers, __contextData phantom field, and processQueuedTask visibility). Let me know if you'd like to discuss any of them.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

The revival dispatch pulled init/set out of a heterogeneous tuple
list, losing the correlation between each tuple's filter and its
init/set node type, which forced two @ts-ignore suppressions at the
call site.  Such suppressions hide any future error on those lines,
so they are unfit for a permanent implementation.  Each entry is now
built by a generic classReviver() factory whose single type parameter
ties the filter to its init/set, letting the compiler check the calls
it previously could not.

Also bind the recursive reviver to one inner closure per decode pass
instead of allocating a fresh closure on every dispatch.

fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-fable-5
The __contextData phantom field binds a TaskDefinition handle to its
federation's context data type, but as a string-keyed property it
leaked into user-facing docs and IDE completions despite its
@internal tag.  Replace it with a module-private unique symbol key:
no value exists at runtime, the marker disappears from completions,
and cross-federation handle rejection still type-checks, now guarded
by a regression test.

Also replace the tasks barrel's wildcard re-export of task.ts with
explicit named exports of the six types its consumers actually use,
so nothing new falls through the barrel unnoticed.

fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-fable-5
@2chanhaeng

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Addressed the codec decision and items 1–2 in e8bd70d, and item 4 in a6f9137. Item 5 is addressed in 39e6ec7—a generic classReviver() factory now ties each filter to its init/set node type, removing both @ts-ignore suppressions. Item 6 is addressed in 5237e13—the __contextData marker is replaced with a module-private unique symbol key, keeping it out of completions while a regression test pins the cross-federation handle rejection. Some incorrect points were not fixed or commented on.

@2chanhaeng

Copy link
Copy Markdown
Contributor Author

@codex review
@gemini-code-assist review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify commits mentioned by the user
for commit in a6f9137ce 39e6ec796 5237e133c; do
  echo "=== Commit: $commit ==="
  gh api repos/fedify-dev/fedify/commits/$commit --jq '.commit.message + "\n---files---"'
  gh api repos/fedify-dev/fedify/commits/$commit --jq '.files[].filename'
  echo ""
done

Length of output: 3116


🏁 Script executed:

#!/bin/bash
# Check the current state of the key files for items 3, 4, 5, 6, 7
# Item 3: resolveTaskQueue silent drop under strict mode
gh api repos/fedify-dev/fedify/contents/packages/fedify/src/federation/middleware.ts?ref=803-add-task-api \
  --jq '.content' | base64 -d | grep -n "resolveTaskQueue\|listenTaskMessage\|processQueuedTask\|queue == null\|queue === null" | head -30

echo "=== `#enqueueSingular` (item 4) ==="
gh api repos/fedify-dev/fedify/contents/packages/fedify/src/federation/context.ts?ref=803-add-task-api \
  --jq '.content' | base64 -d | grep -n "enqueueSingular\|`#enqueueSingular`" | head -20

echo "=== TaskDefinition symbol (item 6) ==="
gh api repos/fedify-dev/fedify/contents/packages/fedify/src/federation/tasks/task.ts?ref=803-add-task-api \
  --jq '.content' | base64 -d | grep -n "symbol\|contextData\|__contextData\|TaskDefinition" | head -30

echo "=== processQueuedTask (item 7) ==="
gh api repos/fedify-dev/fedify/contents/packages/fedify/src/federation/federation.ts?ref=803-add-task-api \
  --jq '.content' | base64 -d | grep -n "processQueuedTask" | head -10

Length of output: 1578


🏁 Script executed:

#!/bin/bash
# Get the correct branch name for PR 803
gh pr view 803 --repo fedify-dev/fedify --json headRefName --jq '.headRefName'

Length of output: 181


🏁 Script executed:

#!/bin/bash
# Check item 4: `#enqueueSingular` renamed to `#encodeTaskMessage`
gh api repos/fedify-dev/fedify/contents/packages/fedify/src/federation/middleware.ts?ref=main \
  --jq '.content' | base64 -d | grep -n "enqueueSingular\|encodeTaskMessage\|enqueueSingular\|`#encode`\|`#enqueue`" | head -20

echo "=== item 3: resolveTaskQueue / silent drop ==="
gh api repos/fedify-dev/fedify/contents/packages/fedify/src/federation/middleware.ts?ref=main \
  --jq '.content' | base64 -d | grep -n "resolveTaskQueue\|queue == null\|queue === null\|giving up\|warn\|listenTask" | head -30

echo "=== item 5: `@ts-ignore` in codec.ts ==="
gh api repos/fedify-dev/fedify/contents/packages/fedify/src/federation/tasks/codec.ts?ref=main \
  --jq '.content' | base64 -d | grep -n "ts-ignore\|classReviver\|ClassReviver" | head -20

echo "=== item 6: TaskDefinition phantom symbol ==="
gh api repos/fedify-dev/fedify/contents/packages/fedify/src/federation/tasks/task.ts?ref=main \
  --jq '.content' | base64 -d | grep -n "symbol\|contextData\|__contextData\|TaskDefinition" | head -30

echo "=== item 7: processQueuedTask ==="
gh api repos/fedify-dev/fedify/contents/packages/fedify/src/federation/federation.ts?ref=main \
  --jq '.content' | base64 -d | grep -n "processQueuedTask" | head -10

Length of output: 2949


🏁 Script executed:

#!/bin/bash
# Get the PR head SHA
HEAD_SHA=$(gh pr view 803 --repo fedify-dev/fedify --json headRefOid --jq '.headRefOid')
echo "HEAD SHA: $HEAD_SHA"

# Check item 4 in context.ts
gh api "repos/fedify-dev/fedify/contents/packages/fedify/src/federation/context.ts?ref=$HEAD_SHA" \
  --jq '.content' | base64 -d | grep -n "enqueueSingular\|encodeTaskMessage" | head -20

echo "=== item 5: codec.ts `@ts-ignore` ==="
gh api "repos/fedify-dev/fedify/contents/packages/fedify/src/federation/tasks/codec.ts?ref=$HEAD_SHA" \
  --jq '.content' | base64 -d | grep -n "ts-ignore\|classReviver\|ClassReviver" | head -20

echo "=== item 6: task.ts symbol ==="
gh api "repos/fedify-dev/fedify/contents/packages/fedify/src/federation/tasks/task.ts?ref=$HEAD_SHA" \
  --jq '.content' | base64 -d | grep -n "symbol\|contextData\|__contextData\|TaskDefinition" | head -30

echo "=== item 3: listenTaskMessage retry/drop ==="
gh api "repos/fedify-dev/fedify/contents/packages/fedify/src/federation/middleware.ts?ref=$HEAD_SHA" \
  --jq '.content' | base64 -d | grep -n "listenTask\|processQueuedTask\|resolveTaskQueue\|giving up\|logger.warn\|queue == null" | grep -i "task\|giving up" | head -30

echo "=== item 7: processQueuedTask visibility ==="
gh api "repos/fedify-dev/fedify/contents/packages/fedify/src/federation/federation.ts?ref=$HEAD_SHA" \
  --jq '.content' | base64 -d | grep -n "processQueuedTask\|`@internal`" | head -20

Length of output: 3630


🏁 Script executed:

#!/bin/bash
HEAD_SHA=$(gh pr view 803 --repo fedify-dev/fedify --json headRefOid --jq '.headRefOid')

echo "=== Item 3 context: listenTaskMessage retry/drop area ==="
gh api "repos/fedify-dev/fedify/contents/packages/fedify/src/federation/middleware.ts?ref=$HEAD_SHA" \
  --jq '.content' | base64 -d | sed -n '2100,2220p'

echo "=== Item 7: processQueuedTask on interface ==="
gh api "repos/fedify-dev/fedify/contents/packages/fedify/src/federation/federation.ts?ref=$HEAD_SHA" \
  --jq '.content' | base64 -d | sed -n '750,770p'

Length of output: 5595



@2chanhaeng: All confirmed — thanks for the careful follow-through on each item.

Verified addressed:

  • Item 4 (a6f9137ce): #enqueueSingular#encodeTaskMessage ✅ (name now accurately reflects that it only encodes, not enqueues)
  • Item 5 (39e6ec796): classReviver<TNode, TOut>() factory ties each filter to its init/set node type — no @ts-ignore remains in codec.ts
  • Item 6 (5237e133c): declare const contextDataBrand: unique symbol with [contextDataBrand]?: TContextData — completely invisible in IDE completions and the regression test guards cross-federation rejection ✅

Items 3 and 7 — you're right, I was wrong:

  • Item 3: The giving up branch in #listenTaskMessage already calls logger.error, so the drop is always observable. My concern about a silent drop was incorrect.
  • Item 7: processQueuedTask is part of the public Federation interface with @since 1.6.0 — it predates this PR and is intentionally public. Marking it @internal would have been incorrect.

The PR looks clean and ready.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a custom background task API to Fedify, allowing applications to define and enqueue arbitrary background jobs with type-safe payloads validated by Standard Schema and serialized via devalue. It includes extensive updates to the core federation engine, context APIs, documentation, and testing suites. The review feedback focuses on the TaskCodec implementation, suggesting a depth limit in the recursive revive function to prevent stack overflows, optimizing the revivers to avoid cloning unchanged subtrees, and registering initialization promises in the seen map immediately to prevent infinite recursion and deadlocks on circular references.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/fedify/src/federation/tasks/codec.ts
Comment thread packages/fedify/src/federation/tasks/codec.ts
Comment thread packages/fedify/src/federation/tasks/codec.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5237e133c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/fedify/src/federation/middleware.ts Outdated
Comment thread packages/testing/src/mock.ts Outdated
Automated reviewers keep proposing a fixed recursion depth cap (~100)
in TaskCodec's #revive to guard against stack overflow from deeply
nested payloads.  The concern does not apply: the revive traversal
suspends at an await on every level, so nesting depth consumes heap
(promise chains) rather than native stack, and a structure deep enough
to threaten the stack would fail inside devalue.parse() before #revive
ever ran.  A cap would only reject legitimate payloads.

Add a regression test that round-trips a payload nested 1,000 levels
deep—an order of magnitude above any proposed cap—through alternating
objects and arrays down to a vocab leaf, so introducing such a cap now
fails the suite.

fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-fable-5
The enqueue guard only checked that the handle's task name existed in
the local registry, so once two federation instances defined the same
task name, a handle from the other instance slipped through: the local
context encoded the payload under the schema carried by the foreign
handle while the worker decoded it under the local definition's schema.
A payload the local schema would have rejected at enqueue thus landed
in the queue anyway, only to be dropped at decode time—defeating the
fail-fast purpose the guard exists for.

defineTask() now stores the exact handle object it returns alongside
the internal definition, and enqueueTask()/enqueueTaskMany() compare
that handle by identity.  Handles still work on every federation built
from the same builder, since build() shares the stored definitions.
The cross-federation regression test now covers the same-name case in
addition to the undefined-name case.

fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-fable-5
MockContext.enqueueTask() invoked the handler with the raw input,
while production enqueueTask() validates the payload against the task
schema and hands the validated output to the handler.  Tests written
against @fedify/testing therefore accepted payloads that production
rejects at enqueue, and observed the raw input rather than the
coerced or normalized value a transforming schema produces—masking
integration bugs the mock exists to surface.

The mock now runs the registered schema's Standard Schema validator
before invoking the handler, throwing the same TypeError production
throws on failure and passing the validated output through.
enqueueTaskMany() inherits this since it delegates to enqueueTask().
Added tests covering a rejected payload, a coercing schema whose
validated output reaches the handler, and per-item validation in the
batch path.

fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-fable-5
The @standard-schema/spec import is shared by the fedify and testing
packages, so it belongs at the workspace level rather than being
declared per package.  The root deno.json already lists it and
workspace members inherit the root import map, making the copy in the
fedify package's deno.json redundant; drop it.  The pnpm side already
sources the version from the catalog in pnpm-workspace.yaml, with each
package.json referencing it as "catalog:".

Assisted-by: Claude Code:claude-fable-5
@2chanhaeng

Copy link
Copy Markdown
Contributor Author

@codex review
@gemini-code-assist review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23fc26384c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/fedify/src/federation/middleware.ts
Comment thread packages/testing/src/mock.ts Outdated
Comment thread packages/testing/src/mock.ts Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a custom background task API to Fedify, allowing developers to define and dispatch arbitrary background jobs with Standard Schema validation and robust serialization using devalue. The feedback highlights a compatibility risk with Array.fromAsync in environments running Node.js versions prior to 22.0.0, suggesting standard for...of loops instead. Additionally, the reviewer recommends implementing a depth limit during deserialization to prevent DoS attacks from deeply nested payloads, and addresses a behavioral discrepancy in the testing mock's enqueueTaskMany validation sequence compared to production.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/fedify/src/federation/tasks/codec.ts
Comment thread packages/fedify/src/federation/tasks/codec.ts
Comment thread packages/fedify/src/federation/tasks/codec.ts
Comment thread packages/testing/src/mock.ts
Every other enqueue path (inbox, outbox, fanout, forwarding) calls
_startQueueInternal() right before enqueuing unless manuallyStartQueue
is set, but #enqueueTasks did not.  An application that only uses the
custom task API never sends an activity, so with the default
configuration its first enqueueTask() accepted the message while no
worker ever listened: tasks piled up in the queue unprocessed until
startQueue() was called explicitly or an activity happened to be sent.

Add the same guard to #enqueueTasks, plus a regression test asserting
that the first enqueue starts the task worker exactly once and that a
second enqueue does not start another listener.

fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-fable-5
Production's enqueueTaskMany() validates and encodes every payload with
Promise.all() before enqueuing anything, so a batch with one invalid
item rejects with no effect.  The mock looped enqueueTask() per item
instead, invoking handlers for earlier payloads before a later one
failed validation—tests could observe a partial processing state that
cannot occur in production.

Split the definition lookup and the schema validation out of
enqueueTask() into helpers, and make enqueueTaskMany() validate the
whole batch up front, running handlers only once every payload has
passed.  The existing batch-validation test now pins that no handler
runs at all when the batch rejects.

fedify-dev#803 (comment)
fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-fable-5
Production compares the registered handle by identity (14313a1), so
passing a handle from another federation instance throws even when both
instances define the same task name.  The mock looked definitions up by
name only, and defineTask() did not keep the handle it returned, so an
identity check was impossible: tests could pass with a handle the real
federation rejects.

Store the returned handle with the definition and require the enqueued
handle to be that very object, with the same error message production
uses.  A regression test defines the same task name on two mock
federations and asserts the foreign handle is rejected without running
any handler.

fedify-dev#803 (comment)

Assisted-by: Claude Code:claude-fable-5
@2chanhaeng 2chanhaeng requested a review from dahlia June 12, 2026 08:33
The custom background task APIs added on this branch were annotated
with @SInCE 2.3.0, but the release that will include them is not yet
decided.  Replace those tags with the placeholder 2.x.x so the
documentation does not promise a specific version prematurely.

Affected APIs: Context.enqueueTask and enqueueTaskMany, the
taskRetryPolicy and taskQueueResolution federation options, the task
queue option, TaskMessage, and the task definition types
(TaskHandler, TaskDefinitionOptions, TaskDefinition, TaskRegistry,
and TaskEnqueueOptions).

Assisted-by: Claude Code:claude-opus-4-8
Local review of the custom background task PR flagged several
documentation-level problems; no runtime behavior is affected:

 -  The tasks manual claimed the API ships in Fedify 2.3.0, while the
    new APIs' JSDoc had already moved to `@since 2.x.x` because the
    containing release is undecided.  Align the manual with the JSDoc.
 -  The manual also claimed a per-task queue defined after the queue
    machinery starts "never gets a worker."  Without
    `manuallyStartQueue`, the next request or enqueue starts the
    worker, so soften the claim to match the implementation.
 -  A comment in codec.test.ts described an instance-level `#seen` map
    that does not exist—each `deserialize()` call builds its own
    per-decode map—and the first test's title claimed a fresh instance
    per operation while the tests share one module-level codec.
    Correct both.
 -  Fix grammar errors in the new *AGENTS.md* paragraph about
    `mise tasks`.

fedify-dev#803

Assisted-by: Claude Code:claude-fable-5
Assisted-by: Codex:gpt-5-5
@2chanhaeng

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b444a6e57a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/fedify/src/federation/middleware.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants