feat(daemon): send session-project path mapping to server#224
Conversation
Parse session_id and workspace from Claude Code statusline input, send the session→project mapping to the daemon via unix socket, which forwards it to the server via POST /api/v1/cc/session-project. This enables the server to fix incorrect pwd values during OTEL ingestion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Summary of ChangesHello @AnnatarHe, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the Claude Code statusline integration by enabling the transmission of session and project directory information. This data is crucial for enriching telemetry events on the server, providing more accurate context for user activity, especially when project paths might otherwise be reported generically. The changes are designed to be efficient, utilizing a fire-and-forget mechanism for daemon communication to avoid performance bottlenecks. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a new feature to send a session-to-project-path mapping from the CLI to the server via the daemon. The changes look good and cover the client, daemon, and model layers appropriately. I've added a couple of comments to improve the robustness of the new daemon logic by adding better error handling and safer type assertions. Once these are addressed, the changes should be ready to merge.
| sessionID, _ := payload["sessionId"].(string) | ||
| projectPath, _ := payload["projectPath"].(string) | ||
| if sessionID != "" && projectPath != "" { | ||
| go model.SendSessionProjectUpdate(context.Background(), *p.config, sessionID, projectPath) | ||
| slog.Debug("session_project update dispatched", slog.String("sessionId", sessionID)) | ||
| } |
There was a problem hiding this comment.
This block has a couple of issues that should be addressed for robustness:
- The type assertions for
sessionIDandprojectPathignore theokboolean return value. This is unsafe because if the payload is malformed (e.g., a field is missing or has the wrong type), this will fail silently. - The error returned by
model.SendSessionProjectUpdateinside the goroutine is ignored. Any error during the API call to the server will be lost, making debugging difficult.
The suggested change below addresses both points by checking the type assertion results and handling the error from the API call by logging it.
sessionID, ok1 := payload["sessionId"].(string)
projectPath, ok2 := payload["projectPath"].(string)
if ok1 && ok2 && sessionID != "" && projectPath != "" {
go func() {
if err := model.SendSessionProjectUpdate(context.Background(), *p.config, sessionID, projectPath); err != nil {
slog.Warn("Failed to send session-project update", slog.Any("err", err), slog.String("sessionId", sessionID))
}
}()
slog.Debug("session_project update dispatched", slog.String("sessionId", sessionID))
} else {
slog.Warn("Received invalid or incomplete session_project payload", "payload", payload)
}| }, | ||
| } | ||
|
|
||
| json.NewEncoder(conn).Encode(msg) |
There was a problem hiding this comment.
The error returned by json.NewEncoder(conn).Encode(msg) is not handled. Even for a fire-and-forget function, it's good practice to log such errors for debugging purposes. You could use the slog package for this, which is used elsewhere in the daemon package (you would need to add the log/slog import).
| func SendSessionProject(socketPath string, sessionID, projectPath string) { | ||
| conn, err := net.DialTimeout("unix", socketPath, 10*time.Millisecond) | ||
| if err != nil { | ||
| return | ||
| } | ||
| defer conn.Close() | ||
|
|
||
| msg := SocketMessage{ | ||
| Type: SocketMessageTypeSessionProject, | ||
| Payload: SessionProjectRequest{ | ||
| SessionID: sessionID, | ||
| ProjectPath: projectPath, | ||
| }, | ||
| } | ||
|
|
||
| json.NewEncoder(conn).Encode(msg) | ||
| } |
There was a problem hiding this comment.
🟡 Missing write deadline on socket connection in SendSessionProject can block the statusline indefinitely
SendSessionProject sets a 10ms dial timeout but never sets a write deadline on the connection. The json.NewEncoder(conn).Encode(msg) call at line 70 could block indefinitely if the daemon accepts the connection but is slow to read.
Root Cause and Impact
Compare with RequestCCInfo at daemon/client.go:82 which properly sets conn.SetDeadline(time.Now().Add(timeout)) before writing. SendSessionProject omits this safeguard.
This function is called synchronously in the statusline command (commands/cc_statusline.go:83) which has a hard 100ms timeout. If the daemon's read buffer is full or the daemon is busy, the write could block for much longer than the expected ~1ms, potentially consuming the entire 100ms budget and causing the statusline to time out. While Unix domain socket writes are typically fast due to kernel buffering, this is a missing safeguard that violates the "fire-and-forget, ~1ms" contract stated in the comment.
Impact: Under load or unusual daemon conditions, the statusline could hang or time out, degrading the user experience.
| func SendSessionProject(socketPath string, sessionID, projectPath string) { | |
| conn, err := net.DialTimeout("unix", socketPath, 10*time.Millisecond) | |
| if err != nil { | |
| return | |
| } | |
| defer conn.Close() | |
| msg := SocketMessage{ | |
| Type: SocketMessageTypeSessionProject, | |
| Payload: SessionProjectRequest{ | |
| SessionID: sessionID, | |
| ProjectPath: projectPath, | |
| }, | |
| } | |
| json.NewEncoder(conn).Encode(msg) | |
| } | |
| func SendSessionProject(socketPath string, sessionID, projectPath string) { | |
| conn, err := net.DialTimeout("unix", socketPath, 10*time.Millisecond) | |
| if err != nil { | |
| return | |
| } | |
| defer conn.Close() | |
| conn.SetDeadline(time.Now().Add(10 * time.Millisecond)) | |
| msg := SocketMessage{ | |
| Type: SocketMessageTypeSessionProject, | |
| Payload: SessionProjectRequest{ | |
| SessionID: sessionID, | |
| ProjectPath: projectPath, | |
| }, | |
| } | |
| json.NewEncoder(conn).Encode(msg) | |
| } |
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
session_idandworkspacefields from Claude Code statusline JSON inputPOST /api/v1/cc/session-project"/"instead of the actual project directoryChanges
model/cc_statusline_types.go: AddSessionIDandWorkspacefields toCCStatuslineInputdaemon/socket.go: Addsession_projectmessage type, struct, and handler casedaemon/client.go: AddSendSessionProject()fire-and-forget socket functionmodel/api_session_project.go(new):SendSessionProjectUpdate()HTTP API callcommands/cc_statusline.go: Wire up session-project send before daemon info queryRelated
Test plan
cc statuslinewith fixture JSON containingsession_idandworkspace, verify daemon receives socket messagego build ./...passes🤖 Generated with Claude Code