Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# AI Agent Guidelines

This document contains critical rules and guidelines for AI agents working on this codebase.

## Security Rules

### CRITICAL: No Dynamic Values in Logs

**All log statements MUST use static strings only. NEVER include dynamic values, regardless of severity.**

#### Bad Examples (DO NOT DO THIS):
```typescript
// BAD - Contains dynamic values
await logger.info(`Task created: ${taskId}`)
await logger.error(`Failed to process ${filename}`)
console.log(`User ${userId} logged in`)
console.error(`Error for ${provider}:`, error)
```

#### Good Examples (DO THIS):
```typescript
// GOOD - Static strings only
await logger.info('Task created')
await logger.error('Failed to process file')
console.log('User logged in')
console.error('Error occurred:', error)
```

#### Rationale:
- **Prevents data leakage**: Dynamic values in logs can expose sensitive information (user IDs, file paths, credentials, etc.) to end users
- **Security by default**: Logs are displayed directly in the UI and returned in API responses
- **No exceptions**: This applies to ALL log levels (info, error, success, command, console.log, console.error, console.warn, etc.)

#### Sensitive Data That Must NEVER Appear in Logs:
- API keys and tokens (ANTHROPIC_API_KEY, OPENAI_API_KEY, GITHUB_TOKEN, etc.)
- Vercel credentials (VERCEL_TOKEN, VERCEL_TEAM_ID, VERCEL_PROJECT_ID)
- User IDs and personal information
- File paths and repository URLs
- Branch names and commit messages
- Error details that may contain sensitive context
- Any dynamic values that could reveal system internals

### Credential Redaction

The `redactSensitiveInfo()` function in `lib/utils/logging.ts` automatically redacts known sensitive patterns, but this is a **backup measure only**. The primary defense is to never log dynamic values in the first place.

#### Current Redaction Patterns:
- API keys (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.)
- GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_)
- Vercel credentials (VERCEL_TOKEN, VERCEL_TEAM_ID, VERCEL_PROJECT_ID)
- Bearer tokens
- JSON fields (teamId, projectId)
- Environment variables containing KEY, TOKEN, SECRET, PASSWORD, TEAM_ID, PROJECT_ID

## Code Quality Guidelines

### Logging Best Practices

1. **Use descriptive static messages**
```typescript
// Instead of logging the value, log the action
await logger.info('Sandbox created successfully')
await logger.info('Dependencies installed')
await logger.error('Build failed')
```

2. **Server-side logging for debugging**
```typescript
// Use console.error for server-side debugging (not shown to users)
// But still avoid sensitive data
console.error('Sandbox creation error:', error)
```

3. **Progress updates**
```typescript
// Use static progress messages
await logger.updateProgress(50, 'Installing dependencies')
await logger.updateProgress(75, 'Running build')
```

### Error Handling

1. **Generic error messages to users**
```typescript
await logger.error('Operation failed')
// NOT: await logger.error(`Operation failed: ${error.message}`)
```

2. **Detailed server-side logging**
```typescript
console.error('Detailed error for debugging:', error)
// This appears in server logs, not user-facing logs
```

## Testing Changes

When making changes that involve logging:

1. **Search for dynamic values**
```bash
# Check for logger statements with template literals
grep -r "logger\.(info|error|success|command)\(\`.*\$\{" .

# Check for console statements with template literals
grep -r "console\.(log|error|warn|info)\(\`.*\$\{" .
```

2. **Verify no sensitive data exposure**
- Test the feature in the UI
- Check the logs displayed to users
- Ensure no sensitive information is visible

## Configuration Security

### Environment Variables

Never expose these in logs or to the client:
- `VERCEL_TOKEN` - Vercel API token
- `VERCEL_TEAM_ID` - Vercel team identifier
- `VERCEL_PROJECT_ID` - Vercel project identifier
- `ANTHROPIC_API_KEY` - Anthropic/Claude API key
- `OPENAI_API_KEY` - OpenAI API key
- `GITHUB_TOKEN` - GitHub API token
- `JWE_SECRET` - Encryption secret
- `ENCRYPTION_KEY` - Encryption key
- Any user-provided API keys

### Client-Safe Variables

Only these variables should be exposed to the client (via `NEXT_PUBLIC_` prefix):
- `NEXT_PUBLIC_AUTH_PROVIDERS` - Available auth providers
- `NEXT_PUBLIC_GITHUB_CLIENT_ID` - GitHub OAuth client ID (public)

## Compliance Checklist

Before submitting changes, verify:

- [ ] No template literals with `${}` in any log statements
- [ ] All logger calls use static strings
- [ ] All console calls use static strings (for user-facing logs)
- [ ] No sensitive data in error messages
- [ ] Tested in UI to confirm no data leakage
- [ ] Server-side debugging logs don't expose credentials

## Questions?

If you need to log information for debugging purposes:
1. Use server-side console logs (not shown to users)
2. Still avoid logging sensitive credentials
3. Consider adding better error handling instead of logging details
4. Use generic user-facing messages

---

**Remember: When in doubt, use a static string. No exceptions.**

6 changes: 3 additions & 3 deletions app/api/tasks/[taskId]/files/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
} catch (branchError: unknown) {
if (branchError && typeof branchError === 'object' && 'status' in branchError && branchError.status === 404) {
// Branch doesn't exist yet (task is still processing)
console.log(`Branch ${task.branchName} doesn't exist yet, returning empty file list`)
console.log('Branch does not exist yet, returning empty file list')
return NextResponse.json({
success: true,
files: [],
Expand Down Expand Up @@ -144,7 +144,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
masterError.status === 404
) {
// Neither main nor master exists, or head branch doesn't exist
console.log(`Could not compare branches for ${task.branchName}`)
console.log('Could not compare branches')
return NextResponse.json({
success: true,
files: [],
Expand All @@ -171,7 +171,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
changes: file.changes || 0,
})) || []

console.log(`Found ${files.length} changed files in branch ${task.branchName}`)
console.log('Found changed files in branch')
} catch (error: unknown) {
console.error('Error fetching file changes from GitHub:', error)

Expand Down
2 changes: 1 addition & 1 deletion app/api/tasks/[taskId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export async function PATCH(request: NextRequest, { params }: RouteParams) {
if (killResult.success) {
await logger.success('Sandbox killed successfully')
} else {
await logger.error(`Failed to kill sandbox: ${killResult.error}`)
await logger.error('Failed to kill sandbox')
}
} catch (killError) {
console.error('Failed to kill sandbox during stop:', killError)
Expand Down
43 changes: 19 additions & 24 deletions app/api/tasks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
})
.where(eq(tasks.id, taskId))

await logger.success(`Generated AI branch name: ${aiBranchName}`)
await logger.success('Generated AI branch name')
} catch (error) {
console.error('Error generating AI branch name:', error)

Expand All @@ -139,7 +139,7 @@
.where(eq(tasks.id, taskId))

const logger = createTaskLogger(taskId)
await logger.info(`Using fallback branch name: ${fallbackBranchName}`)
await logger.info('Using fallback branch name')
} catch (dbError) {
console.error('Error updating task with fallback branch name:', dbError)
}
Expand Down Expand Up @@ -275,10 +275,10 @@
) {
let sandbox: Sandbox | null = null
const logger = createTaskLogger(taskId)
const taskStartTime = Date.now()

Check warning on line 278 in app/api/tasks/route.ts

View workflow job for this annotation

GitHub Actions / checks

'taskStartTime' is assigned a value but never used

try {
console.log(`[TASK ${taskId}] Starting task processing at ${new Date().toISOString()}`)
console.log('Starting task processing')

// Update task status to processing with real-time logging
await logger.updateStatus('processing', 'Task created, preparing to start...')
Expand Down Expand Up @@ -310,13 +310,13 @@
}

if (aiBranchName) {
await logger.info(`Using AI-generated branch name: ${aiBranchName}`)
await logger.info('Using AI-generated branch name')
} else {
await logger.info('AI branch name not ready, will use fallback during sandbox creation')
}

await logger.updateProgress(15, 'Creating sandbox environment...')
console.log(`[TASK ${taskId}] Creating sandbox at ${new Date().toISOString()}`)
await logger.updateProgress(15, 'Creating sandbox environment')
console.log('Creating sandbox')

// Create sandbox with progress callback and 5-minute timeout
const sandboxResult = await createSandbox(
Expand Down Expand Up @@ -371,7 +371,7 @@

const { sandbox: createdSandbox, domain, branchName } = sandboxResult
sandbox = createdSandbox || null
console.log(`[TASK ${taskId}] Sandbox created successfully at ${new Date().toISOString()}`)
console.log('Sandbox created successfully')

// Update sandbox URL and branch name (only update branch name if not already set by AI)
const updateData: { sandboxUrl?: string; updatedAt: Date; branchName?: string } = {
Expand All @@ -393,8 +393,8 @@
}

// Log agent execution start
await logger.updateProgress(50, `Installing and executing ${selectedAgent} agent...`)
console.log(`[TASK ${taskId}] Starting ${selectedAgent} agent execution at ${new Date().toISOString()}`)
await logger.updateProgress(50, 'Installing and executing agent')
console.log('Starting agent execution')

// Execute selected agent with timeout (different timeouts per agent)
const getAgentTimeout = (agent: string) => {
Expand Down Expand Up @@ -447,9 +447,7 @@
})

if (mcpServers.length > 0) {
await logger.info(
`Found ${mcpServers.length} connected MCP servers: ${mcpServers.map((s) => s.name).join(', ')}`,
)
await logger.info('Found connected MCP servers')

// Store MCP server IDs in the task
await db
Expand Down Expand Up @@ -484,15 +482,15 @@
agentTimeoutPromise,
])

console.log(`[TASK ${taskId}] Agent execution completed at ${new Date().toISOString()}`)
console.log('Agent execution completed')

if (agentResult.success) {
// Log agent completion
await logger.success(`${selectedAgent} agent execution completed`)
await logger.info(agentResult.output || 'Code changes applied successfully')
await logger.success('Agent execution completed')
await logger.info('Code changes applied successfully')

if (agentResult.agentResponse) {
await logger.info(`Agent Response: ${agentResult.agentResponse}`)
await logger.info('Agent response received')
}

// Agent execution logs are already logged in real-time by the agent
Expand All @@ -508,7 +506,7 @@
if (shutdownResult.success) {
await logger.success('Sandbox shutdown completed')
} else {
await logger.error(`Sandbox shutdown failed: ${shutdownResult.error}`)
await logger.error('Sandbox shutdown failed')
}

// Check if push failed and handle accordingly
Expand All @@ -521,14 +519,11 @@
await logger.updateStatus('completed')
await logger.updateProgress(100, 'Task completed successfully')

const totalTaskTime = ((Date.now() - taskStartTime) / 1000).toFixed(2)
console.log(
`[TASK ${taskId}] Task completed successfully at ${new Date().toISOString()} (total time: ${totalTaskTime}s)`,
)
console.log('Task completed successfully')
}
} else {
// Agent failed, but we still want to capture its logs
await logger.error(`${selectedAgent} agent execution failed`)
await logger.error('Agent execution failed')

// Agent execution logs are already logged in real-time by the agent
// No need to log them again here
Expand All @@ -546,7 +541,7 @@
if (shutdownResult.success) {
await logger.info('Sandbox shutdown completed after error')
} else {
await logger.error(`Sandbox shutdown failed: ${shutdownResult.error}`)
await logger.error('Sandbox shutdown failed')
}
} catch (shutdownError) {
console.error('Failed to shutdown sandbox after error:', shutdownError)
Expand All @@ -557,7 +552,7 @@
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'

// Log the error and update task status
await logger.error(`Error: ${errorMessage}`)
await logger.error('Error occurred during task processing')
await logger.updateStatus('error', errorMessage)
}
}
Expand Down
2 changes: 1 addition & 1 deletion components/task-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
{ value: 'opencode', label: 'opencode', icon: OpenCode },
] as const

const AGENT_MODELS = {

Check warning on line 67 in components/task-details.tsx

View workflow job for this annotation

GitHub Actions / checks

'AGENT_MODELS' is assigned a value but never used
claude: [
{ value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' },
{ value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' },
Expand Down Expand Up @@ -306,7 +306,7 @@
newDiffsCache[filename] = result.data
}
} catch (err) {
console.error(`Error fetching diff for ${filename}:`, err)
console.error('Error fetching diff for file:', err)
}
})

Expand Down
2 changes: 1 addition & 1 deletion components/task-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ export function TaskForm({
setLoadingRepos(false)
return
} catch {
console.warn(`Failed to parse cached repos for ${selectedOwner}, fetching fresh data`)
console.warn('Failed to parse cached repos, fetching fresh data')
sessionStorage.removeItem(cacheKey)
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/api-keys/user-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export async function getUserApiKey(provider: Provider): Promise<string | undefi
return decrypt(userKey[0].value)
}
} catch (error) {
console.error(`Error fetching user API key for ${provider}:`, error)
console.error('Error fetching user API key:', error)
}

return systemKeys[provider]
Expand Down
Loading
Loading