Skip to content

Commit 349668f

Browse files
committed
security: remove session.share, add Slack system context and debugging docs
- Remove client.session.share() call that uploaded session content (including code context) to opncd.ai — security risk for private repos - Inject Slack mrkdwn formatting instructions as system-reminder on the first prompt of each thread so LLM responses render properly - Add debugging guide to both README.md and AGENTS.md: log locations, failure investigation steps, common failure modes, verbose logging Signed-off-by: xuezhaojun <xuezhaokeepgoing@gmail.com>
1 parent 8ba1549 commit 349668f

3 files changed

Lines changed: 154 additions & 13 deletions

File tree

AGENTS.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,54 @@ Plugins may run in multiple Agent pods simultaneously. Design for this:
134134
- Session maps are keyed by unique identifiers (e.g., Slack channel + thread timestamp)
135135
- Bounded collections with eviction to prevent memory leaks
136136

137+
## Debugging Plugins
138+
139+
### Log Locations
140+
141+
OpenCode writes structured logs (session lifecycle, LLM calls, tool execution, errors) to:
142+
143+
```
144+
~/.local/share/opencode/log/ # macOS/Linux default ($XDG_DATA_HOME/opencode/log/)
145+
```
146+
147+
Files are named `YYYY-MM-DDTHHMMSS.log` (one per startup, 10 most recent retained). Run `opencode debug paths` to confirm the path on your system.
148+
149+
Plugin `console.log`/`console.warn`/`console.error` output goes to the OpenCode process stdout/stderr — it is **not** captured in the structured log file. Use the `[plugin-name]` prefix convention to make plugin output easy to filter.
150+
151+
### Tracing a Problem
152+
153+
1. **Find the session** — search the log for the session ID or creation event:
154+
```bash
155+
grep "ses_XXXXX" ~/.local/share/opencode/log/*.log
156+
```
157+
158+
2. **Check for errors** — LLM failures, processor crashes, permission timeouts:
159+
```bash
160+
grep "ERROR.*ses_XXXXX" ~/.local/share/opencode/log/*.log
161+
```
162+
163+
3. **Key log markers**:
164+
- `service=session ... created` — session created
165+
- `service=session.prompt step=N loop` — prompt loop iteration N
166+
- `service=llm ... stream` — LLM call started
167+
- `service=llm ... stream error` — LLM call failed (root cause in `error=` JSON)
168+
- `service=session.processor ... error=` — processor crash
169+
- `service=session.prompt ... exiting loop` — prompt completed normally
170+
171+
### CLI Flags for Debugging
172+
173+
```bash
174+
opencode serve --print-logs # logs to stderr instead of file
175+
opencode serve --log-level DEBUG # maximum verbosity
176+
```
177+
178+
Both can also be set in `opencode.json`:
179+
```json
180+
{
181+
"logLevel": "DEBUG"
182+
}
183+
```
184+
137185
## Repository Structure
138186

139187
```

opencode-slack-plugin/README.md

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ The plugin activates automatically when both `SLACK_BOT_TOKEN` and `SLACK_APP_TO
120120
- **DM the bot** directly for private conversations
121121
- **@mention the bot** in a channel to start a threaded conversation
122122
- Each Slack thread creates a separate OpenCode session with its own context
123-
- Session share links are posted automatically when a new thread starts
124123

125124
### Permission Requests
126125

@@ -149,6 +148,87 @@ Completed tool calls are posted to the thread in real time:
149148
*bash* - ran tests
150149
```
151150

151+
## Debugging
152+
153+
### Log Locations
154+
155+
The plugin produces two categories of logs:
156+
157+
| Source | Location | Contents |
158+
|--------|----------|----------|
159+
| **OpenCode structured log** | `~/.local/share/opencode/log/` | Session lifecycle, LLM calls, tool execution, permission events, errors. File per startup, 10 most recent retained. |
160+
| **Plugin console output** | OpenCode process stdout/stderr | `[slack-plugin]` prefixed messages: connection status, session creation, typing indicators, prompt results. |
161+
162+
To find the current log directory:
163+
164+
```bash
165+
opencode debug paths # prints all paths including "log"
166+
```
167+
168+
On macOS the default is `~/.local/share/opencode/log/`. On Linux it follows `$XDG_DATA_HOME/opencode/log/`.
169+
170+
### Investigating a Failed Slack Message
171+
172+
When a Slack message gets no reply, follow this sequence:
173+
174+
**1. Find the session ID** — look for `[slack-plugin] Created session` in stdout, or find the session by thread timestamp:
175+
176+
```bash
177+
grep "Slack thread" ~/.local/share/opencode/log/2026-05-08T*.log
178+
```
179+
180+
**2. Trace the session in the structured log** — search for the session ID to see the full lifecycle:
181+
182+
```bash
183+
grep "ses_XXXXX" ~/.local/share/opencode/log/2026-05-08T*.log
184+
```
185+
186+
Key events to look for:
187+
188+
| Log entry | Meaning |
189+
|-----------|---------|
190+
| `service=session ... created` | Session was created successfully |
191+
| `service=session.prompt step=0 loop` | LLM prompt loop started |
192+
| `service=llm ... stream` | LLM call initiated |
193+
| `service=llm ... stream error` | LLM call failed (check `error=` field) |
194+
| `service=session.processor ... error=` | Processor crashed while handling LLM response |
195+
| `service=session.prompt ... exiting loop` | Prompt completed normally |
196+
| `service=permission ... evaluated` | Permission was auto-resolved or user-replied |
197+
198+
**3. Check for LLM provider errors** — filter for ERROR level:
199+
200+
```bash
201+
grep "ERROR.*ses_XXXXX" ~/.local/share/opencode/log/2026-05-08T*.log
202+
```
203+
204+
### Common Failure Modes
205+
206+
**Bot shows "is thinking..." but never replies:**
207+
208+
The plugin's `session.prompt()` returned data with no text parts. This happens when the LLM provider fails. Check the structured log for `stream error` — common causes:
209+
210+
- **OAuth token expired** (Google Vertex): `POST https://oauth2.googleapis.com/token` fails. Fix: `gcloud auth application-default login`
211+
- **Proxy misconfiguration**: The LLM provider's HTTP client inherits `http_proxy`/`https_proxy` env vars. If your proxy is down, LLM calls fail silently.
212+
- **Rate limiting**: Look for HTTP 429 in the error JSON.
213+
214+
**Bot creates a session but the second message in the thread is ignored:**
215+
216+
The thread requires `@mention` for each message in channel threads (by design — prevents capturing human-to-human conversation). DMs do not require `@mention`.
217+
218+
**"Session idle" appears in stdout but no Slack message:**
219+
220+
This means `session.prompt()` completed but returned empty text parts. Search the structured log for `ERROR` entries on that session ID to find the root cause.
221+
222+
### Enabling Verbose Logging
223+
224+
```bash
225+
# Print logs to stderr instead of file (useful for local debugging)
226+
opencode serve --print-logs
227+
228+
# Set log level to DEBUG for maximum detail
229+
opencode serve --log-level DEBUG
230+
```
231+
152232
## KubeOpenCode Integration
153233

154234
When running inside a KubeOpenCode Agent, the plugin additionally:

opencode-slack-plugin/src/index.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type SessionEntry = {
1313
channel: string
1414
thread: string
1515
lastActive: number
16+
firstPrompt: boolean // true until the first prompt is sent
1617
// Per-thread pending permission — avoids cross-thread interference
1718
// when multiple users chat with the bot simultaneously.
1819
// Stores both requestId (for new API) and permissionId/sessionId (for v1 fallback).
@@ -33,6 +34,19 @@ const MAX_SESSIONS = 500
3334
const LOG_PREFIX = "[slack-plugin]"
3435
const PERMISSION_TIMEOUT_MS = 5 * 60_000 // 5 min — auto-clear stale permission
3536

37+
// Injected as the first message in each new Slack thread session.
38+
// Tells the LLM it is responding in Slack and should use Slack mrkdwn formatting.
39+
const SLACK_SYSTEM_CONTEXT = [
40+
"This conversation is happening in Slack.",
41+
"Format your responses using Slack mrkdwn syntax:",
42+
"- *bold*, _italic_, ~strikethrough~, `inline code`",
43+
"- ```code blocks``` (no language specifier after backticks)",
44+
"- Bulleted lists with • or -",
45+
"- Links: <url|display text>",
46+
"- Do NOT use Markdown headings (#, ##), HTML tags, or [text](url) link syntax — they do not render in Slack.",
47+
"Keep responses concise and conversational.",
48+
].join("\n")
49+
3650
// ---------------------------------------------------------------------------
3751
// PromptQueue: serializes prompt calls per session
3852
// ---------------------------------------------------------------------------
@@ -355,6 +369,7 @@ const slack = async (input: PluginInput): Promise<Hooks> => {
355369
channel,
356370
thread,
357371
lastActive: Date.now(),
372+
firstPrompt: true,
358373
pendingPermission: null,
359374
}
360375
sessions.set(key, entry)
@@ -365,17 +380,6 @@ const slack = async (input: PluginInput): Promise<Hooks> => {
365380
evictOldestSession()
366381
}
367382

368-
// Log session share link (not posted to Slack — too low-level for users).
369-
// Visible in OpenCode server logs: look for "[slack-plugin] Session share:"
370-
try {
371-
const shareResult = await client.session.share({ path: { id: result.data.id } })
372-
if (!shareResult.error && shareResult.data?.share?.url) {
373-
console.log(`${LOG_PREFIX} Session share: ${shareResult.data.share.url} (thread: ${key})`)
374-
}
375-
} catch {
376-
// Share is optional
377-
}
378-
379383
return entry
380384
}
381385

@@ -507,13 +511,22 @@ const slack = async (input: PluginInput): Promise<Hooks> => {
507511
// Show typing indicator while processing
508512
setTypingStatus(channel, thread)
509513

514+
// Prepend Slack formatting context on the first prompt of each thread.
515+
// Subsequent messages in the same thread skip this — the LLM already has
516+
// the context in its conversation history.
517+
let promptText = text
518+
if (session.firstPrompt) {
519+
promptText = `<system-reminder>\n${SLACK_SYSTEM_CONTEXT}\n</system-reminder>\n\n${text}`
520+
session.firstPrompt = false
521+
}
522+
510523
// Enqueue the prompt to serialize concurrent messages in the same session.
511524
// OpenCode silently drops prompt() calls on a busy session (the message is
512525
// written to DB but runLoop never starts), so we must serialize here.
513526
promptQueue.enqueue(session.sessionId, async () => {
514527
const result = await client.session.prompt({
515528
path: { id: session.sessionId },
516-
body: { parts: [{ type: "text", text }] },
529+
body: { parts: [{ type: "text", text: promptText }] },
517530
})
518531

519532
if (result.error) {

0 commit comments

Comments
 (0)