You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Iris chat's tracked-item store (iris.contextStore in globalState) accumulates courses and exercises and never removes them again. A course or exercise that no longer exists on the server, or that the student is no longer enrolled in, stays in the topic picker and the course popover indefinitely. Selecting one fails, and the failure is reported as if a retry could help.
This is not a regression from #375 — origin/dev behaves the same way. Filed separately so it does not block that PR.
What a user sees
Courses from past semesters, or courses they were unenrolled from, remain listed in the course popover forever.
A deleted exercise stays in the topic picker until its stored due date passes. With no due date, or one far in the future, it stays permanently.
Clicking such an entry produces Could not open that course. Please try again. — the server said 404, so retrying will never work.
Reproduced locally: after deleting 24 courses server-side, all 24 were still offered in the picker, and the server was returning only the 3 courses the student is actually in (/api/core/courses/for-dashboard).
Why it happens
ContextStore.snapshot() (extension/src/extension/services/iris/context/contextStore.ts:67) filters exercises on three criteria only — is it the workspace exercise, is it the current topic, is its stored deadline still in the future — and does not filter courses at all:
Server-side existence is not a criterion anywhere. removeExercise and removeCourse exist on both ContextStore and TrackedItemRepository, but nothing in src/ calls them — the only callers are in extension/test/unit/provider/contextStore.test.ts. registerCourse / registerExercise add or update; nothing ever removes.
What the fix should not be
Pruning everything absent from the dashboard response would be wrong. A course is also missing from that list when it has ended, has not started yet, or when the request simply failed. That would empty the picker on one flaky request and throw away the history of a finished course that the student may still want to read.
The rule this codebase already has
IrisConversationService already draws exactly the right distinction for conversation rows (extension/src/extension/services/iris/conversation/conversationService.ts:380):
400 (wrong course / not a chat session) and 404 (absent) mean the row is wrong. 403, 5xx and network failures may be transient and the conversation may still exist, so the row stays and the caller reports rather than forgets.
Conversations are forgotten on a definitive gone signal via _forgetSession. Tracked courses and exercises are not, although the removal methods for them already exist. Applying the same rule to the store is the consistent fix: forget on 400/404 from an explicit open, keep on 403/5xx/network, never react to mere absence from a list.
Related defect in the same store: entries are not scoped per server
StoredState holds { version, courses, exercises } and keys both collections by bare numeric id. There is no server identity anywhere in the key. Switching between Artemis instances therefore merges their data, and ids collide because they are only unique per server.
Observed on a real store, after using the same VS Code profile against a remote Artemis and a local one:
Exercise 1
Exercise 2
Exercise 3
Server
course 9026
course 9026
course 9028
Store
course 1
course 1
course 1
Course 1 does not exist on that server at all; it came from a different instance. This is worse than stale data, because the store now asserts a wrong course for an exercise that does exist, and the picker and "Ask Iris about this exercise" both read that mapping.
Cleaning up cannot fix this one — the key needs the server identity, which means a key change plus a migration. Possibly worth splitting into its own issue.
Suggested direction
Forget a tracked course or exercise when opening it yields a definitive 400/404, mirroring _forgetSession. Leave 403, 5xx and network failures alone.
Report a permanently gone entry as gone rather than advising a retry. Iris chat follows the server's conversation model #375 does this for a course with Iris disabled (errorKey: iris.course_disabled); a deleted course still gets the generic retry wording.
Scope the store per server, or clear it when the configured artemis.serverUrl changes.
The Iris chat's tracked-item store (
iris.contextStoreinglobalState) accumulates courses and exercises and never removes them again. A course or exercise that no longer exists on the server, or that the student is no longer enrolled in, stays in the topic picker and the course popover indefinitely. Selecting one fails, and the failure is reported as if a retry could help.This is not a regression from #375 —
origin/devbehaves the same way. Filed separately so it does not block that PR.What a user sees
Could not open that course. Please try again.— the server said 404, so retrying will never work.Reproduced locally: after deleting 24 courses server-side, all 24 were still offered in the picker, and the server was returning only the 3 courses the student is actually in (
/api/core/courses/for-dashboard).Why it happens
ContextStore.snapshot()(extension/src/extension/services/iris/context/contextStore.ts:67) filters exercises on three criteria only — is it the workspace exercise, is it the current topic, is its stored deadline still in the future — and does not filter courses at all:Server-side existence is not a criterion anywhere.
removeExerciseandremoveCourseexist on bothContextStoreandTrackedItemRepository, but nothing insrc/calls them — the only callers are inextension/test/unit/provider/contextStore.test.ts.registerCourse/registerExerciseadd or update; nothing ever removes.What the fix should not be
Pruning everything absent from the dashboard response would be wrong. A course is also missing from that list when it has ended, has not started yet, or when the request simply failed. That would empty the picker on one flaky request and throw away the history of a finished course that the student may still want to read.
The rule this codebase already has
IrisConversationServicealready draws exactly the right distinction for conversation rows (extension/src/extension/services/iris/conversation/conversationService.ts:380):Conversations are forgotten on a definitive gone signal via
_forgetSession. Tracked courses and exercises are not, although the removal methods for them already exist. Applying the same rule to the store is the consistent fix: forget on 400/404 from an explicit open, keep on 403/5xx/network, never react to mere absence from a list.Related defect in the same store: entries are not scoped per server
StoredStateholds{ version, courses, exercises }and keys both collections by bare numeric id. There is no server identity anywhere in the key. Switching between Artemis instances therefore merges their data, and ids collide because they are only unique per server.Observed on a real store, after using the same VS Code profile against a remote Artemis and a local one:
Course 1 does not exist on that server at all; it came from a different instance. This is worse than stale data, because the store now asserts a wrong course for an exercise that does exist, and the picker and "Ask Iris about this exercise" both read that mapping.
Cleaning up cannot fix this one — the key needs the server identity, which means a key change plus a migration. Possibly worth splitting into its own issue.
Suggested direction
_forgetSession. Leave 403, 5xx and network failures alone.errorKey: iris.course_disabled); a deleted course still gets the generic retry wording.artemis.serverUrlchanges.