From 5839ff2355b822c540c59821a4db397db8c95e00 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 23:08:42 -0700 Subject: [PATCH 1/2] Fix session name too long error when creating from issues (#324) Truncate auto-generated session names from issue titles to 100 chars, add maxLength to the name input field, and use a shared constant for the limit across client and server validation. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/new/form-reducer.test.ts | 21 ++++++++++++++++++++- src/app/new/form-reducer.ts | 4 +++- src/app/new/page.tsx | 9 +++++---- src/server/routers/sessions.ts | 3 ++- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/app/new/form-reducer.test.ts b/src/app/new/form-reducer.test.ts index 1b2949f9..74256ada 100644 --- a/src/app/new/form-reducer.test.ts +++ b/src/app/new/form-reducer.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { formReducer, initialFormState } from './form-reducer'; +import { formReducer, initialFormState, SESSION_NAME_MAX_LENGTH } from './form-reducer'; import type { FormState } from './form-reducer'; import type { Repo } from '@/components/RepoSelector'; import type { Issue } from '@/lib/types'; @@ -204,6 +204,25 @@ describe('formReducer', () => { expect(result.initialPrompt).toBe(''); }); + it('truncates session name to max length when issue title is very long', () => { + const longTitle = 'A'.repeat(200); + const longIssue: Issue = { + ...mockIssue, + number: 1, + title: longTitle, + }; + const state: FormState = { + ...initialFormState, + selectedRepo: mockRepo, + selectedBranch: 'main', + }; + + const result = formReducer(state, { type: 'selectIssue', issue: longIssue }); + + expect(result.sessionName.length).toBe(SESSION_NAME_MAX_LENGTH); + expect(result.sessionName).toBe(`#1: ${longTitle}`.slice(0, SESSION_NAME_MAX_LENGTH)); + }); + it('preserves session name when issue is deselected and name was manually edited', () => { const state: FormState = { ...initialFormState, diff --git a/src/app/new/form-reducer.ts b/src/app/new/form-reducer.ts index a089b76a..6b42fb22 100644 --- a/src/app/new/form-reducer.ts +++ b/src/app/new/form-reducer.ts @@ -1,6 +1,8 @@ import type { Repo } from '@/components/RepoSelector'; import type { Issue } from '@/lib/types'; +export const SESSION_NAME_MAX_LENGTH = 100; + export interface FormState { selectedRepo: Repo | null; selectedBranch: string; @@ -44,7 +46,7 @@ export function formReducer(state: FormState, action: FormAction): FormState { sessionName: state.nameManuallyEdited ? state.sessionName : action.issue - ? `#${action.issue.number}: ${action.issue.title}` + ? `#${action.issue.number}: ${action.issue.title}`.slice(0, SESSION_NAME_MAX_LENGTH) : '', initialPrompt: state.promptManuallyEdited ? state.initialPrompt diff --git a/src/app/new/page.tsx b/src/app/new/page.tsx index c0f8eaf7..323a20c0 100644 --- a/src/app/new/page.tsx +++ b/src/app/new/page.tsx @@ -18,7 +18,7 @@ import { BranchSelector } from '@/components/BranchSelector'; import { IssueSelector } from '@/components/IssueSelector'; import type { Repo } from '@/components/RepoSelector'; import type { Issue } from '@/lib/types'; -import { formReducer, initialFormState } from './form-reducer'; +import { formReducer, initialFormState, SESSION_NAME_MAX_LENGTH } from './form-reducer'; function generateIssuePrompt(issue: Issue, repoFullName: string): string { const issueUrl = `https://github.com/${repoFullName}/issues/${issue.number}`; @@ -115,9 +115,9 @@ function NewSessionForm() { return; } - const defaultName = isNoRepo - ? 'Workspace' - : `${form.selectedRepo.name} - ${form.selectedBranch}`; + const defaultName = ( + isNoRepo ? 'Workspace' : `${form.selectedRepo.name} - ${form.selectedBranch}` + ).slice(0, SESSION_NAME_MAX_LENGTH); createMutation.mutate({ name: form.sessionName || defaultName, @@ -164,6 +164,7 @@ function NewSessionForm() { type="text" value={form.sessionName} onChange={handleNameChange} + maxLength={SESSION_NAME_MAX_LENGTH} placeholder={ isNoRepo ? 'Workspace' diff --git a/src/server/routers/sessions.ts b/src/server/routers/sessions.ts index d20abfb3..21e1ada7 100644 --- a/src/server/routers/sessions.ts +++ b/src/server/routers/sessions.ts @@ -13,6 +13,7 @@ import { runClaudeCommand, stopSession } from '../services/claude-runner'; import { sseEvents } from '../services/events'; import { createLogger, toError } from '@/lib/logger'; import { env } from '@/lib/env'; +import { SESSION_NAME_MAX_LENGTH } from '@/app/new/form-reducer'; const log = createLogger('sessions'); @@ -111,7 +112,7 @@ export const sessionsRouter = router({ create: protectedProcedure .input( z.object({ - name: z.string().min(1).max(100), + name: z.string().min(1).max(SESSION_NAME_MAX_LENGTH), repoFullName: z .string() .regex(/^[\w-]+\/[\w.-]+$/) From 9c776b73d30c19af0400dc6c658e5d3385027d49 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 23:12:31 -0700 Subject: [PATCH 2/2] Move SESSION_NAME_MAX_LENGTH to shared types Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/new/form-reducer.test.ts | 3 ++- src/app/new/form-reducer.ts | 4 +--- src/app/new/page.tsx | 3 ++- src/lib/types.ts | 3 +++ src/server/routers/sessions.ts | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/app/new/form-reducer.test.ts b/src/app/new/form-reducer.test.ts index 74256ada..d5ac6ed3 100644 --- a/src/app/new/form-reducer.test.ts +++ b/src/app/new/form-reducer.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { formReducer, initialFormState, SESSION_NAME_MAX_LENGTH } from './form-reducer'; +import { formReducer, initialFormState } from './form-reducer'; +import { SESSION_NAME_MAX_LENGTH } from '@/lib/types'; import type { FormState } from './form-reducer'; import type { Repo } from '@/components/RepoSelector'; import type { Issue } from '@/lib/types'; diff --git a/src/app/new/form-reducer.ts b/src/app/new/form-reducer.ts index 6b42fb22..cc19df07 100644 --- a/src/app/new/form-reducer.ts +++ b/src/app/new/form-reducer.ts @@ -1,7 +1,5 @@ import type { Repo } from '@/components/RepoSelector'; -import type { Issue } from '@/lib/types'; - -export const SESSION_NAME_MAX_LENGTH = 100; +import { SESSION_NAME_MAX_LENGTH, type Issue } from '@/lib/types'; export interface FormState { selectedRepo: Repo | null; diff --git a/src/app/new/page.tsx b/src/app/new/page.tsx index 323a20c0..0b22ab3b 100644 --- a/src/app/new/page.tsx +++ b/src/app/new/page.tsx @@ -18,7 +18,8 @@ import { BranchSelector } from '@/components/BranchSelector'; import { IssueSelector } from '@/components/IssueSelector'; import type { Repo } from '@/components/RepoSelector'; import type { Issue } from '@/lib/types'; -import { formReducer, initialFormState, SESSION_NAME_MAX_LENGTH } from './form-reducer'; +import { SESSION_NAME_MAX_LENGTH } from '@/lib/types'; +import { formReducer, initialFormState } from './form-reducer'; function generateIssuePrompt(issue: Issue, repoFullName: string): string { const issueUrl = `https://github.com/${repoFullName}/issues/${issue.number}`; diff --git a/src/lib/types.ts b/src/lib/types.ts index 754c5b97..44e373db 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -19,6 +19,9 @@ export const MessageType = { export type MessageType = (typeof MessageType)[keyof typeof MessageType]; +// Session constants +export const SESSION_NAME_MAX_LENGTH = 100; + // Session interface export interface Session { id: string; diff --git a/src/server/routers/sessions.ts b/src/server/routers/sessions.ts index 21e1ada7..4fcaa6f1 100644 --- a/src/server/routers/sessions.ts +++ b/src/server/routers/sessions.ts @@ -13,7 +13,7 @@ import { runClaudeCommand, stopSession } from '../services/claude-runner'; import { sseEvents } from '../services/events'; import { createLogger, toError } from '@/lib/logger'; import { env } from '@/lib/env'; -import { SESSION_NAME_MAX_LENGTH } from '@/app/new/form-reducer'; +import { SESSION_NAME_MAX_LENGTH } from '@/lib/types'; const log = createLogger('sessions');