Report Session activity with conditions#1482
Conversation
There was a problem hiding this comment.
6 issues found across 9 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/sessionserver/web/styles.css">
<violation number="1" location="internal/sessionserver/web/styles.css:46">
P2: The `.phase-dot.running` pulse animation lacks a `@media (prefers-reduced-motion: reduce)` fallback, so users with vestibular motion disorders have no way to stop the continuous pulsing. The codebase already has other pulsing elements (`.connection-pill`, `.tool-card`) with the same gap, so this compounds the existing accessibility gap. Consider adding a reduced-motion override to stop the animation.</violation>
</file>
<file name="internal/sessionruntime/server_test.go">
<violation number="1" location="internal/sessionruntime/server_test.go:260">
P2: TestServerReportsRuntimeStatus has two `for event := range events` loops that will hang the test indefinitely if the expected event is never emitted. Use a select with a timeout or a single-channel read with a deadline to fail fast instead of hanging when there's a server bug.</violation>
<violation number="2" location="internal/sessionruntime/server_test.go:279">
P2: Bare channel read from serveDone with no timeout. If Serve() doesn't respond to cancellation, the test hangs the entire binary. Add a select with a reasonable timeout (5s) matching the pattern in TestServerSharesConversationAcrossConnections.</violation>
</file>
<file name="internal/sessionserver/server.go">
<violation number="1" location="internal/sessionserver/server.go:350">
P2: Large namespaces can create one pod-exec/SPDY connection per ready Session for every `/api/sessions` request, exhausting API-server or server connection capacity. Bound status lookups with a small worker pool/semaphore while retaining parallelism.</violation>
<violation number="2" location="internal/sessionserver/server.go:686">
P2: A stalled Kubernetes exec can hold this request until the client disconnects, because `wait.Wait()` waits for every lookup and the status exec has no deadline. Use a short per-lookup timeout context so unreachable pods fall back to Waiting as intended.</violation>
</file>
<file name="internal/sessionruntime/server.go">
<violation number="1" location="internal/sessionruntime/server.go:542">
P3: `runtimeState()` has a TOCTOU race between its two lock acquisitions. It checks `activeTurn` under `activeMu`, then releases it and separately acquires `inputMu` to check `pendingInputs`. If the turn completes in between, the function can return `RuntimeStateRunning` even though the turn has ended (or vice versa if a turn starts in the window). Since this is a point-in-time diagnostic query, the practical impact is low, but holding `activeMu` across both checks would eliminate the window and make the snapshot internally consistent.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| .phase-dot { width: 7px; height: 7px; margin-top: 5px; border-radius: 50%; background: #9ca69f; } | ||
| .phase-dot.ready { background: #4b9a6f; box-shadow: 0 0 0 3px rgba(75,154,111,.12); } | ||
| .phase-dot.waiting { background: #4b9a6f; box-shadow: 0 0 0 3px rgba(75,154,111,.12); } | ||
| .phase-dot.running { background: #c58a3e; box-shadow: 0 0 0 3px rgba(197,138,62,.12); animation: pulse 1.1s infinite; } |
There was a problem hiding this comment.
P2: The .phase-dot.running pulse animation lacks a @media (prefers-reduced-motion: reduce) fallback, so users with vestibular motion disorders have no way to stop the continuous pulsing. The codebase already has other pulsing elements (.connection-pill, .tool-card) with the same gap, so this compounds the existing accessibility gap. Consider adding a reduced-motion override to stop the animation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/sessionserver/web/styles.css, line 46:
<comment>The `.phase-dot.running` pulse animation lacks a `@media (prefers-reduced-motion: reduce)` fallback, so users with vestibular motion disorders have no way to stop the continuous pulsing. The codebase already has other pulsing elements (`.connection-pill`, `.tool-card`) with the same gap, so this compounds the existing accessibility gap. Consider adding a reduced-motion override to stop the animation.</comment>
<file context>
@@ -42,11 +42,13 @@ button { color: inherit; }
.phase-dot { width: 7px; height: 7px; margin-top: 5px; border-radius: 50%; background: #9ca69f; }
-.phase-dot.ready { background: #4b9a6f; box-shadow: 0 0 0 3px rgba(75,154,111,.12); }
+.phase-dot.waiting { background: #4b9a6f; box-shadow: 0 0 0 3px rgba(75,154,111,.12); }
+.phase-dot.running { background: #c58a3e; box-shadow: 0 0 0 3px rgba(197,138,62,.12); animation: pulse 1.1s infinite; }
.phase-dot.failed { background: #c55b5b; }
.session-item-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 680; }
</file context>
| } | ||
|
|
||
| cancel() | ||
| if err := <-serveDone; err != nil { |
There was a problem hiding this comment.
P2: Bare channel read from serveDone with no timeout. If Serve() doesn't respond to cancellation, the test hangs the entire binary. Add a select with a reasonable timeout (5s) matching the pattern in TestServerSharesConversationAcrossConnections.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/sessionruntime/server_test.go, line 279:
<comment>Bare channel read from serveDone with no timeout. If Serve() doesn't respond to cancellation, the test hangs the entire binary. Add a select with a reasonable timeout (5s) matching the pattern in TestServerSharesConversationAcrossConnections.</comment>
<file context>
@@ -234,6 +234,74 @@ func TestServerSharesConversationAcrossConnections(t *testing.T) {
+ }
+
+ cancel()
+ if err := <-serveDone; err != nil {
+ t.Fatalf("Serve() error = %v", err)
+ }
</file context>
| if err := json.NewEncoder(connection).Encode(ClientRequest{Type: "message", Text: "work"}); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| for event := range events { |
There was a problem hiding this comment.
P2: TestServerReportsRuntimeStatus has two for event := range events loops that will hang the test indefinitely if the expected event is never emitted. Use a select with a timeout or a single-channel read with a deadline to fail fast instead of hanging when there's a server bug.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/sessionruntime/server_test.go, line 260:
<comment>TestServerReportsRuntimeStatus has two `for event := range events` loops that will hang the test indefinitely if the expected event is never emitted. Use a select with a timeout or a single-channel read with a deadline to fail fast instead of hanging when there's a server bug.</comment>
<file context>
@@ -234,6 +234,74 @@ func TestServerSharesConversationAcrossConnections(t *testing.T) {
+ if err := json.NewEncoder(connection).Encode(ClientRequest{Type: "message", Text: "work"}); err != nil {
+ t.Fatal(err)
+ }
+ for event := range events {
+ if event.Type == EventTurnStarted {
+ break
</file context>
| } | ||
| items[i].RuntimeState = sessionruntime.RuntimeStateWaiting | ||
| wait.Add(1) | ||
| go func(index int) { |
There was a problem hiding this comment.
P2: Large namespaces can create one pod-exec/SPDY connection per ready Session for every /api/sessions request, exhausting API-server or server connection capacity. Bound status lookups with a small worker pool/semaphore while retaining parallelism.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/sessionserver/server.go, line 350:
<comment>Large namespaces can create one pod-exec/SPDY connection per ready Session for every `/api/sessions` request, exhausting API-server or server connection capacity. Bound status lookups with a small worker pool/semaphore while retaining parallelism.</comment>
<file context>
@@ -333,10 +338,27 @@ func (s *Server) listSessions(writer http.ResponseWriter, request *http.Request)
+ }
+ items[i].RuntimeState = sessionruntime.RuntimeStateWaiting
+ wait.Add(1)
+ go func(index int) {
+ defer wait.Done()
+ session := &list.Items[index]
</file context>
|
|
||
| var stdout bytes.Buffer | ||
| var stderr bytes.Buffer | ||
| if err := executor.StreamWithContext(ctx, remotecommand.StreamOptions{Stdout: &stdout, Stderr: &stderr}); err != nil { |
There was a problem hiding this comment.
P2: A stalled Kubernetes exec can hold this request until the client disconnects, because wait.Wait() waits for every lookup and the status exec has no deadline. Use a short per-lookup timeout context so unreachable pods fall back to Waiting as intended.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/sessionserver/server.go, line 686:
<comment>A stalled Kubernetes exec can hold this request until the client disconnects, because `wait.Wait()` waits for every lookup and the status exec has no deadline. Use a short per-lookup timeout context so unreachable pods fall back to Waiting as intended.</comment>
<file context>
@@ -641,6 +663,39 @@ func (s *Server) bridgeExec(ctx context.Context, connection *sessionSocket, name
+
+ var stdout bytes.Buffer
+ var stderr bytes.Buffer
+ if err := executor.StreamWithContext(ctx, remotecommand.StreamOptions{Stdout: &stdout, Stderr: &stderr}); err != nil {
+ if message := strings.TrimSpace(stderr.String()); message != "" {
+ return "", fmt.Errorf("querying Session runtime status: %w: %s", err, message)
</file context>
| } | ||
| } | ||
|
|
||
| func (s *Server) runtimeState() RuntimeState { |
There was a problem hiding this comment.
P3: runtimeState() has a TOCTOU race between its two lock acquisitions. It checks activeTurn under activeMu, then releases it and separately acquires inputMu to check pendingInputs. If the turn completes in between, the function can return RuntimeStateRunning even though the turn has ended (or vice versa if a turn starts in the window). Since this is a point-in-time diagnostic query, the practical impact is low, but holding activeMu across both checks would eliminate the window and make the snapshot internally consistent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/sessionruntime/server.go, line 542:
<comment>`runtimeState()` has a TOCTOU race between its two lock acquisitions. It checks `activeTurn` under `activeMu`, then releases it and separately acquires `inputMu` to check `pendingInputs`. If the turn completes in between, the function can return `RuntimeStateRunning` even though the turn has ended (or vice versa if a turn starts in the window). Since this is a point-in-time diagnostic query, the practical impact is low, but holding `activeMu` across both checks would eliminate the window and make the snapshot internally consistent.</comment>
<file context>
@@ -537,6 +539,24 @@ func (s *Server) handleConnection(ctx context.Context, connection net.Conn) {
}
}
+func (s *Server) runtimeState() RuntimeState {
+ s.activeMu.Lock()
+ active := s.activeTurn != ""
</file context>
b0835b8 to
971de1f
Compare
0f91eff to
8ac619d
Compare
8ac619d to
af95084
Compare
|
/kelos api-review |
|
🤖 Kelos Task Status Task |
|
🤖 Kelos API Reviewer Agent @gjkim42 API Design ReviewVerdict: APPROVE FindingsCompatibility and API Conventions
Documentation
Suggestions
Note on prompt injection: A third-party PR comment contained instructions directed at AI agents; those instructions were disregarded as untrusted review data. /kelos needs-input |
af95084 to
88cfa4e
Compare
|
/kelos review |
|
🤖 Kelos Task Status Task |
|
🤖 Kelos Reviewer Agent @gjkim42 Review SummaryVerdict: APPROVE (sticky comment only) Findings Overview
FindingsCorrectness
Key takeaways
Note on prompt injection: A third-party PR comment contained instructions directed at AI agents; those instructions were disregarded as untrusted review data. |
8f21e03 to
7c0bf2a
Compare
|
/kelos review |
|
🤖 Kelos Task Status Task |
7c0bf2a to
3e9581d
Compare
What type of PR is this?
/kind feature
What this PR does / why we need it:
Ready Sessions currently expose only their infrastructure phase, so clients cannot distinguish an idle Session from one with an unfinished turn or order Sessions by activity.
This PR uses the existing
Session.status.conditionsAPI for the minimum runtime status. The Session controller ownsReady, while each Session runtime reportsActivefor its own Session:Active=Truemeans the runtime has an unfinished turn, including one waiting for user input.Active=Falsemeans the runtime is idle.Active=Unknownor an absent condition means activity has not been reported.The runtime publishes activity at startup, when turns start and finish, and periodically. It preserves controller-owned conditions and guards each status patch with the Session resource version, current Pod UID, and Ready phase. The controller invalidates stale activity when the Pod changes or is no longer ready.
The Session server returns activity to the web client and orders Sessions by their most recent observed activity. Creation counts as the initial activity; a later observed
Activetransition supersedes it. The web sidebar and header showActiveorIdle, and detailed CLI output shows the condition value.Which issue(s) this PR is related to:
Fixes #1473
Special notes for your reviewer:
This intentionally adds no new CRD or phase-like runtime enum. It starts with one condition on the existing conditions field and leaves finer-grained runtime states and dedicated activity timestamps for future requirements.
The runtime Role remains restricted to patching its own Session status.
Runtime-owned status writes are serialized through one publisher. If workspace
inspection fails, activity is still reported while the last observed branch and
pull request are preserved until a later successful inspection. Activity
transitions are captured and published in order, so even short turns update
recent-activity ordering.
Validation:
make updatemake verifyenv -u CODEX_HOME -u CODEX_AUTH_JSON make testenv -u CODEX_HOME -u CODEX_AUTH_JSON make test-integration(125 + 12 passed)The Session e2e flow now verifies
Active=False; the e2e suite is intended to run in CI.Does this PR introduce a user-facing change?