fix: require valid project directory for session creation

- Backend: POST /api/chat/sessions now validates working_directory
  exists (400 MISSING_DIRECTORY / INVALID_DIRECTORY)
- db.ts: remove process.cwd() fallback in createSession
- ChatListPanel: validate last-working-directory before use,
  open FolderPicker when invalid; add SSR guard for localStorage
- NewChatPage: block sending without a project directory selected

Bump version to 0.10.5.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
7418andClaude Opus 4.6 committed 2026-02-13 15:37:51 +08:00
1 parent ae8a905581
commit 916b8889b7
6 files changed
+63 -12

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "codepilot",
"version": "0.10.4",
"version": "0.10.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codepilot",
"version": "0.10.4",
"version": "0.10.5",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.33",
"@hugeicons/core-free-icons": "^3.1.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "codepilot",
"version": "0.10.4",
"version": "0.10.5",
"private": true,
"author": {
"name": "op7418",
+20
View File
@@ -1,4 +1,5 @@
import { NextRequest } from 'next/server';
import fs from 'fs/promises';
import { getAllSessions, createSession } from '@/lib/db';
import type { CreateSessionRequest, SessionsResponse, SessionResponse } from '@/types';
@@ -17,6 +18,25 @@ export async function GET() {
export async function POST(request: NextRequest) {
try {
const body: CreateSessionRequest = await request.json();
// Validate working_directory is provided
if (!body.working_directory) {
return Response.json(
{ error: 'Working directory is required', code: 'MISSING_DIRECTORY' },
{ status: 400 },
);
}
// Validate directory actually exists on disk
try {
await fs.access(body.working_directory);
} catch {
return Response.json(
{ error: 'Directory does not exist', code: 'INVALID_DIRECTORY' },
{ status: 400 },
);
}
const session = createSession(
body.title,
body.model,
+16 -4
View File
@@ -78,6 +78,20 @@ export default function NewChatPage() {
async (content: string) => {
if (isStreaming) return;
// Require a project directory before sending
if (!workingDir.trim()) {
const hint: Message = {
id: 'hint-' + Date.now(),
session_id: '',
role: 'assistant',
content: '**Please select a project directory first.** Use the folder picker in the toolbar below to choose a working directory before sending a message.',
created_at: new Date().toISOString(),
token_usage: null,
};
setMessages((prev) => [...prev, hint]);
return;
}
setIsStreaming(true);
setStreamingContent('');
setToolUses([]);
@@ -90,14 +104,12 @@ export default function NewChatPage() {
let sessionId = '';
try {
// Create a new session with optional working directory
// Create a new session with working directory
const createBody: Record<string, string> = {
title: content.slice(0, 50),
mode,
working_directory: workingDir.trim(),
};
if (workingDir.trim()) {
createBody.working_directory = workingDir.trim();
}
const createRes = await fetch('/api/chat/sessions', {
method: 'POST',
+23 -4
View File
@@ -53,6 +53,7 @@ function formatRelativeTime(dateStr: string): string {
const COLLAPSED_PROJECTS_KEY = "codepilot:collapsed-projects";
function loadCollapsedProjects(): Set<string> {
if (typeof window === 'undefined') return new Set();
try {
const raw = localStorage.getItem(COLLAPSED_PROJECTS_KEY);
if (raw) return new Set(JSON.parse(raw));
@@ -129,27 +130,45 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) {
const [creatingChat, setCreatingChat] = useState(false);
const handleNewChat = useCallback(async () => {
const lastDir = localStorage.getItem("codepilot:last-working-directory");
const lastDir = typeof window !== 'undefined'
? localStorage.getItem("codepilot:last-working-directory")
: null;
if (!lastDir) {
router.push("/chat");
// No saved directory — let user pick one
setFolderPickerOpen(true);
return;
}
// Validate the saved directory still exists
setCreatingChat(true);
try {
const checkRes = await fetch(
`/api/files/browse?dir=${encodeURIComponent(lastDir)}`
);
if (!checkRes.ok) {
// Directory is gone — clear stale value and prompt user
localStorage.removeItem("codepilot:last-working-directory");
setFolderPickerOpen(true);
return;
}
const res = await fetch("/api/chat/sessions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ working_directory: lastDir }),
});
if (!res.ok) {
router.push("/chat");
// Backend rejected it (e.g. INVALID_DIRECTORY) — prompt user
localStorage.removeItem("codepilot:last-working-directory");
setFolderPickerOpen(true);
return;
}
const data = await res.json();
router.push(`/chat/${data.session.id}`);
window.dispatchEvent(new CustomEvent("session-created"));
} catch {
router.push("/chat");
setFolderPickerOpen(true);
} finally {
setCreatingChat(false);
}
+1 -1
View File
@@ -229,7 +229,7 @@ export function createSession(
const db = getDb();
const id = crypto.randomBytes(16).toString('hex');
const now = new Date().toISOString().replace('T', ' ').split('.')[0];
const wd = workingDirectory || process.cwd();
const wd = workingDirectory || '';
const projectName = path.basename(wd);
db.prepare(