Environment
- Package/tool: @fedify/lint (oxlint plugin)
- Runtime: Node.js
- Operating system: Linux (WSL2)
Steps to reproduce
Register an actor dispatcher that returns null for "not found" (as documented in ActorDispatcher's JSDoc) before returning a Person with id, inbox, and endpoints.sharedInbox all set:
federation.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
const user = /* lookup */;
if (user == null) return null;
return new Person({
id: ctx.getActorUri(identifier),
inbox: ctx.getInboxUri(identifier),
endpoints: new Endpoints({ sharedInbox: ctx.getInboxUri() }),
});
});
Run oxlint on the file.
Expected behavior
No warning/error. The Person-returning branch already sets id, inbox, and endpoints.sharedInbox as recommended, and returning null for "actor not found" is explicitly documented as valid:
ActorDispatcher's return type includes | null (packages/fedify/src/federation/callback.ts).
setActorDispatcher's JSDoc says the dispatcher "may return an actor, a Tombstone, or null if the actor is not found" (packages/fedify/src/federation/federation.ts).
- The handler treats a
null return as a normal 404, not an error (packages/fedify/src/federation/handler.ts).
Actual behavior
actor-id-required (error), actor-inbox-property-required, and actor-shared-inbox-property-required all fire on the dispatcher, even
though the Person-returning path is fully compliant.
Cause
In packages/lint/src/lib/property-checker.ts, checkAllReturnPaths requires every return statement in the dispatcher body to satisfy the property checker:
const checkAllReturnPaths = (propertyChecker: PropertyChecker) =>
(node) =>
pipe(
node,
collectReturnPaths,
cases(isEmpty, always(false), every(checkReturnStatement(propertyChecker))),
);
checkReturnStatement treats a non-object argument (such as null) as an automatic failure via always(false):
const checkReturnStatement =
(propertyChecker: PropertyChecker) => (node: ReturnStatement) =>
pipe(
node,
prop("argument"),
cases(isObject, checkBranchWith(propertyChecker), always(false)),
);
Since every() requires all collected return paths to pass, a documented return null; early-exit branch makes the whole function fail the check, regardless of whether the actual actor-returning branch is compliant.
Proposed fix
Exclude early-exit return null; (and possibly return new Tombstone(...), which also isn't subject to these property requirements) from the paths checkAllReturnPaths requires to carry the checked property -- only returns of an actor object should be checked.
Notes
Found while adding a DB-backed actor dispatcher for a small ActivityPub project. Verified by removing the null branch: oxlint reports 0
warnings/errors with just return new Person({...}), and reports the 3 false positives above as soon as the if (user == null) return null; branch is added back.
AI usage disclosure
This issue draft was assisted-by Claude Code:claude-sonnet-5, which helped read through @fedify/lint's source and trace the root cause. The reproduction, cited source locations, and final write-up were reviewed and verified by me before submitting.
Environment
Steps to reproduce
Register an actor dispatcher that returns
nullfor "not found" (as documented inActorDispatcher's JSDoc) before returning aPersonwithid,inbox, andendpoints.sharedInboxall set:Run
oxlinton the file.Expected behavior
No warning/error. The
Person-returning branch already setsid,inbox, andendpoints.sharedInboxas recommended, and returningnullfor "actor not found" is explicitly documented as valid:ActorDispatcher's return type includes| null(packages/fedify/src/federation/callback.ts).setActorDispatcher's JSDoc says the dispatcher "may return an actor, aTombstone, ornullif the actor is not found" (packages/fedify/src/federation/federation.ts).nullreturn as a normal 404, not an error (packages/fedify/src/federation/handler.ts).Actual behavior
actor-id-required(error),actor-inbox-property-required, andactor-shared-inbox-property-requiredall fire on the dispatcher, eventhough the
Person-returning path is fully compliant.Cause
In
packages/lint/src/lib/property-checker.ts,checkAllReturnPathsrequires everyreturnstatement in the dispatcher body to satisfy the property checker:checkReturnStatementtreats a non-objectargument(such asnull) as an automatic failure viaalways(false):Since
every()requires all collected return paths to pass, a documentedreturn null;early-exit branch makes the whole function fail the check, regardless of whether the actual actor-returning branch is compliant.Proposed fix
Exclude early-exit
return null;(and possiblyreturn new Tombstone(...), which also isn't subject to these property requirements) from the pathscheckAllReturnPathsrequires to carry the checked property -- onlyreturns of an actor object should be checked.Notes
Found while adding a DB-backed actor dispatcher for a small ActivityPub project. Verified by removing the
nullbranch:oxlintreports 0warnings/errors with just
return new Person({...}), and reports the 3 false positives above as soon as theif (user == null) return null;branch is added back.AI usage disclosure
This issue draft was assisted-by Claude Code:claude-sonnet-5, which helped read through
@fedify/lint's source and trace the root cause. The reproduction, cited source locations, and final write-up were reviewed and verified by me before submitting.