Skip to content

Commit 285f9b5

Browse files
committed
Fix Linear issues showing as unread for the creator
1 parent ec2fe37 commit 285f9b5

1 file changed

Lines changed: 58 additions & 16 deletions

File tree

connectors/linear/src/linear.ts

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ type SyncState = {
5454
initialSync: boolean;
5555
};
5656

57+
type ViewerInfo = {
58+
linearId: string;
59+
email: string;
60+
name: string;
61+
avatar?: string;
62+
};
63+
5764
/**
5865
* Linear project management source
5966
*
@@ -104,6 +111,39 @@ export class Linear extends Connector<Linear> {
104111
return new LinearClient({ accessToken: token.token });
105112
}
106113

114+
/**
115+
* Resolve author contact from a Linear user object.
116+
* Falls back to cached viewer info when the API doesn't return an email.
117+
*/
118+
private async resolveAuthorContact(
119+
user: { id?: string; email?: string; name?: string; avatarUrl?: string | null } | null | undefined,
120+
projectId: string
121+
): Promise<NewContact | undefined> {
122+
if (!user) return undefined;
123+
124+
if (user.email) {
125+
return {
126+
email: user.email,
127+
name: user.name ?? "",
128+
avatar: user.avatarUrl ?? undefined,
129+
};
130+
}
131+
132+
// Linear API often omits email on creator relations — check if it's the authenticated user
133+
if (user.id) {
134+
const viewerInfo = await this.get<ViewerInfo>(`viewer_info_${projectId}`);
135+
if (viewerInfo?.email && user.id === viewerInfo.linearId) {
136+
return {
137+
email: viewerInfo.email,
138+
name: user.name || viewerInfo.name,
139+
avatar: user.avatarUrl ?? viewerInfo.avatar,
140+
};
141+
}
142+
}
143+
144+
return undefined;
145+
}
146+
107147
/**
108148
* Returns available Linear teams as channel resources.
109149
*/
@@ -263,6 +303,22 @@ export class Linear extends Connector<Linear> {
263303
projectId: string,
264304
options?: ProjectSyncOptions
265305
): Promise<void> {
306+
// Cache the authenticated user's info for author resolution
307+
try {
308+
const client = await this.getClient(projectId);
309+
const viewer = await client.viewer;
310+
if (viewer) {
311+
await this.set(`viewer_info_${projectId}`, {
312+
linearId: viewer.id,
313+
email: viewer.email,
314+
name: viewer.name,
315+
avatar: viewer.avatarUrl ?? undefined,
316+
});
317+
}
318+
} catch (error) {
319+
console.warn("Failed to fetch Linear viewer info:", error);
320+
}
321+
266322
// Initialize sync state
267323
await this.set(`sync_state_${projectId}`, {
268324
after: null,
@@ -392,16 +448,9 @@ export class Linear extends Connector<Linear> {
392448
}
393449

394450
// Prepare author and assignee contacts - will be passed directly as NewContact
395-
let authorContact: NewContact | undefined;
451+
const authorContact = await this.resolveAuthorContact(creator, projectId);
396452
let assigneeContact: NewContact | undefined;
397453

398-
if (creator?.email) {
399-
authorContact = {
400-
email: creator.email,
401-
name: creator.name,
402-
avatar: creator.avatarUrl ?? undefined,
403-
};
404-
}
405454
if (assignee?.email) {
406455
assigneeContact = {
407456
email: assignee.email,
@@ -688,16 +737,9 @@ export class Linear extends Connector<Linear> {
688737
const assignee = issue.assignee || null;
689738

690739
// Build thread update with only issue fields (no notes)
691-
let authorContact: NewContact | undefined;
740+
const authorContact = await this.resolveAuthorContact(creator, projectId);
692741
let assigneeContact: NewContact | undefined;
693742

694-
if (creator?.email) {
695-
authorContact = {
696-
email: creator.email,
697-
name: creator.name,
698-
avatar: creator.avatarUrl ?? undefined,
699-
};
700-
}
701743
if (assignee?.email) {
702744
assigneeContact = {
703745
email: assignee.email,

0 commit comments

Comments
 (0)