Skip to content

Iris chat cannot start a conversation in an exercise that has none: the session API changed upstream #373

Description

@Predixx

Observed

Opening exercise Graph Traversal (id 3), the chat shows "Iris is temporarily unavailable. Retry to reload." The extension log:

Fetched 0 session(s) with messages from Artemis
No sessions found, creating a new one
Creating NEW Iris session for exercise 3
[ERROR] Error creating new Iris session: ApiError: API request failed: 400: error.http.400

Nothing appears in the Artemis log for this request, because Spring rejects it before the controller is entered and the LoggingAspect only logs on method entry. The getAllSessionsForCourse call one line earlier is logged normally.

Why now

Upstream commit 0f98d7a185 (2026-07-13, "Iris: Enable users to change the context of an active conversation" #12696) reworked the session API. The local Artemis checkout picked it up with the upstream merge on 2026-07-25 01:46. The sessions that still work (id=5 course, id=4 exercise 2) were created 2026-07-24 23:06, i.e. before that merge, and no new session has been created since — until this one. So the defect has been latent for two days and only fires on a context that has no session yet.

It also only became reachable because #371 was fixed: before that the chat always fell back to the course, which does have a session, so this path was never taken.

Three distinct defects

1. The 400: wrong endpoint for creating a session

artemisApi.ts:687 posts mode + entityId:

const params = new URLSearchParams({ mode, entityId: String(entityId) });
await this.makeRequest(`/api/iris/chat/sessions?${params}`, { method: 'POST' });

IrisChatSessionResource.java:126 now requires a course id and nothing else:

@PostMapping("sessions")
public ResponseEntity<…> createCourseSession(@RequestParam long courseId)

A missing required @RequestParam is a 400 before the method body. The endpoint was split in two:

Endpoint Params Meaning
POST sessions/current mode, entityId get the session for this context, create if needed
POST sessions courseId create a new empty course session ("New Chat")

The extension sends the right parameters to the wrong one.

2. The trap: sessions/current does not create an exercise session

This is the more dangerous one, and it invalidates the obvious fix of "just call sessions/current instead".

IrisChatSessionService.java:409 routes PROGRAMMING_EXERCISE_CHAT to findExerciseSessionOrCourseFallback, which is (:523):

return irisChatSessionRepository.findLatestByEntityIdAndChatModeAndUserIdWithMessages(...)
        .stream().findFirst()
        .orElseGet(() -> findOrCreateEmptyCourseSession(exercise.getCourseViaExerciseGroupOrCourseMember(), user));

So for an exercise with no session, it returns a COURSE session. The extension parses only id out of the response (artemisApi.ts:693) and never inspects mode on this path, so it would store a course session while the header says "Graph Traversal (Workspace)" — silently reintroducing exactly the wrong-context class of bug that #371 just removed.

3. The missing step: the extension never sends pendingContext

Upstream's new mechanism for making a session exercise-scoped is a context change carried on the message POST (IrisMessageResource.java:126-131):

var pendingContext = requestDTO.pendingContext();
if (pendingContext != null) {
    irisChatSessionService.applyContextChange(chatSession, pendingContext.mode(), pendingContext.entityId(), user);
}

Artemis uses that sequence itself in onBuildFailure (IrisChatSessionService.java:277-280):

var session = findExerciseSessionOrCourseFallback(exercise, user, PROGRAMMING_EXERCISE_CHAT);
if (session.getMode() == COURSE_CHAT) {
    applyContextChange(session, PROGRAMMING_EXERCISE_CHAT, exercise.getId(), user);
}

sendChatMessage (artemisApi.ts:494-505) builds { sentAt, content, uncommittedFiles? } and never sends pendingContext. So even once defect 1 is fixed, a conversation opened on an exercise would stay course-scoped and Iris would answer without the exercise context.

Proposed fix

Follow the sequence Artemis uses itself:

  1. POST sessions/current?mode&entityId to obtain a session (drop the POST sessions call from this path entirely).
  2. Read mode off the response. If it came back as COURSE_CHAT while an exercise context is active, the session still has to be re-pointed.
  3. Send pendingContext: { mode, entityId } on the first message so the session is switched to the exercise, and render the CTXSWAP marker Artemis persists for it.

Step 3 is the part with real design weight: it means a conversation's scope changes at send time, not at open time, so the UI has to be honest about which context the next message will go to.

How the web client does it (this settles the design)

iris-chat-http.service.ts:121-126, the client's own doc comment:

/**
 * Creates a new (empty) course chat session ("New Chat"). Every new session is a course session;
 * exercise/lecture context is layered on later via a context switch.
 */
createCourseSession(courseId: number)

So the "new chat" pencil in the exercise view does work, and the way it works confirms the fix above: it creates a course session and layers the exercise on via the context switch. The context chip the client shows in its composer (e.g. "Sorting Algorithms") is that pending context, displayed before it has been applied.

That is not a workaround, it is the intended model: a conversation is course-scoped until a context switch points it at an exercise, and the switch travels with the first message.

Consequently the in our chat header should stay and be implemented the same way, rather than being hidden for exercise contexts.

Acceptance

  • Opening an exercise that has never had a conversation shows a working, empty chat rather than "Iris is temporarily unavailable".
  • The conversation the student ends up in is scoped to the exercise shown in the header, verified against mode/entityId on the session, not assumed.
  • Sending the first message in such a conversation reaches Iris with the exercise context.
  • The action either works for exercises or is not offered there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions