fix: corrige 3 travamentos no startup e runtime (v0.17.0)#8
Conversation
Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
Add getFeedbackMood() helper that scans memory files for feedback rules and derives emotional state from their average score. Integrated into the getMood() priority chain between sonolento and error-rate checks. Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
Add 3 new achievements based on totalFeedbackConfirms thresholds: - feedback-aprendiz at 5 confirms - feedback-mestre at 15 confirms - feedback-sabio at 30 confirms Includes test suite covering unlock/lock boundary conditions. Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
- Import FeedbackDetectionResult type - Add correction, undo, and confirm reply arrays - Accept optional feedbackResult in fireCompanionObserver - React to detected feedback with deterministic replies and memory - Export notifyFeedbackConfirm (+2 XP, stats, reply) - Export notifyFeedbackRuleCreated (stats increment) Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
Show feedback rules and confirmations count when non-zero, with separator line for visual consistency. Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
Notify buddy and grant +2 XP when user confirms a feedback pattern via /feedback confirm. Appends buddy reaction to confirmation message. Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
… grant - Moved feedback reaction block before bash success handler to avoid early returns from git status checks silently skipping feedback - Unified XP grant + stat increment in single saveGlobalConfig callback to avoid race conditions between separate writes Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
- Add getLastDetectedFeedback()/clearLastDetectedFeedback() to feedbackHook.ts - Store last detected result in module-level variable after detection - REPL.tsx reads and passes feedbackResult to fireCompanionObserver - Clear after use to prevent stale reactions Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
- Feedback XP source (+2 on /feedback confirm) - Feedback moods (orgulhoso/preocupado/neutro) - Feedback tip skill (getFeedbackTip) - 3 new achievements (Aprendiz/Mestre/Sábio) - Feedback stats in /buddy stats - Updated reactions table Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
…itions - Scan recent messages (last 10) instead of only last message - Find tool results and task completions across the full sequence - Remove unused oldInfo variable from notifyFeedbackConfirm - Add stoneageFirst memory trigger - Add stoneage.test.ts and stoneage-hooks.test.ts Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
- Bump ws to ^8.21.0 - Add uuid ^11.1.1 override to fix gaxios subdependency vulnerability - Update bun.lock Co-Authored-By: OpenClaude (gemini-3.1-pro-preview) <openclaude@gitlawb.com>
…rnate screen mode The startup logo was previously printed synchronously to `stdout` during CLI startup. When the Ink UI subsequently initialized in fullscreen/alternate screen mode, it would switch to the alternate screen buffer, instantly clearing the terminal and causing the logo to flash and vanish. This fixes the issue by skipping the stdout print if `isFullscreenEnvEnabled()` is true. We preserve the standard React `LogoV2` component which mounts immediately and shows the logo correctly within the interactive REPL without any performance or flickering issues.
…rnate screen mode The startup logo was previously printed synchronously to `stdout` during CLI startup. When the Ink UI subsequently initialized in fullscreen/alternate screen mode, it would switch to the alternate screen buffer, instantly clearing the terminal and causing the logo to flash and vanish. This fixes the issue by: 1. Skipping the stdout print if `isFullscreenEnvEnabled()` is true. 2. Rendering the gradient startup logo natively inside Ink (via `<Ansi>` within `Messages.tsx`) so it persists at the top of the chat history, seamlessly replacing the old LogoV2 box without the flickering artifact.
…ender useMainLoopModel() subscribes to AppState + GrowthBook refresh → triggers forceRerender → text memo invalidates → loop. LogoHeader now memoizes getStartupScreenText() once (detectProvider auto-resolves via env vars). Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
…> for Logo The previous commit moved the startup logo into the main React tree using `<Ansi>`. Because the gradient logo consists of hundreds of discrete color ansi-spans, the Ink Ansi parser choked on initialization trying to shred and convert the raw codes into hundreds of Yoga flex leaves, locking the CPU and freezing the CLI. Replaced `<Ansi>` with the dedicated `<RawAnsi>` component which bypasses the parser entirely, acting as a fixed-bounds (width=62) Yoga leaf and streaming the raw escape strings straight to stdout. Co-Authored-By: OpenClaude (gemini-3.1-pro-preview) <openclaude@gitlawb.com>
…<RawAnsi> for Logo" This reverts commit e595d9d5c69b5bc6108732bfebcfaad2a1dc00f4.
…ite re-render" This reverts commit 16ae1a5adbd9cc66936123c03cdde561ae8e9b68.
- fs.watch deadlock no Bun: fallback para chokidar com usePolling quando detectado runtime Bun (oven-sh/bun#27469) - Keychain macOS sem timeout: adiciona timeout 1.5s em todas as chamadas ao `security find-generic-password` - Detecção de IDE travando: substitui N chamadas síncronas por getAncestorCommandsAsync único com timeout 3s - Adiciona documentação detalhada em docs/fix-hang-v0.17.0.md Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
Reviewer's GuideImplements v0.17.0 stability fixes for startup/runtime hangs (Bun file watching, macOS keychain, IDE detection), enriches the Buddy companion system with stoneage + feedback integration, and refactors startup screen rendering while adding tests and dependency bumps. Sequence diagram for new buddy feedback integration and reactionssequenceDiagram
actor User
participant REPL as REPL
participant FeedbackHook as feedbackHook
participant Observer as fireCompanionObserver
participant Config as config
User->>REPL: message (correction/undo)
REPL->>FeedbackHook: detectAndLogFeedback(input, messages, sessionId)
FeedbackHook-->>FeedbackHook: set lastDetectedFeedback
FeedbackHook-->>REPL: FeedbackDetectionResult
Note over REPL: Later, at end of query loop
REPL->>FeedbackHook: getLastDetectedFeedback()
FeedbackHook-->>REPL: FeedbackDetectionResult
REPL->>Observer: fireCompanionObserver(messages, onReaction, feedbackResult)
Observer->>Config: saveGlobalConfig(update companionMemory)
Observer-->>REPL: onReaction(buddy reaction)
REPL->>FeedbackHook: clearLastDetectedFeedback()
FeedbackHook-->>REPL: (cleared)
Note over User,REPL: On /feedback confirm
User->>REPL: /feedback confirm
REPL->>Observer: notifyFeedbackConfirm(buddyName)
Observer->>Config: saveGlobalConfig(update xp, totalFeedbackConfirms)
Observer-->>REPL: buddy reaction text
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- Now that
companionStatsincludestotalTokensSaved,totalFeedbackRulesandtotalFeedbackConfirms, make sure every place that creates or resetscompanionStatsinitializes these fields to0so achievements/moods/UI never seeundefinedvalues. - The stoneage tracking in
fireCompanionObserveris purely based on/\bstoneage\b/iin user text; consider wiring this to the actual stoneage activation path so that stats and achievements can’t be incremented accidentally just by mentioning the word in normal conversation. - With
getMoodbecoming async and doing a filesystem-backed feedback scan, double-check all call sites await it and consider a stricter internal timeout/fallback so/buddyand related commands can’t be noticeably delayed if the memory directory is large or slow.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Now that `companionStats` includes `totalTokensSaved`, `totalFeedbackRules` and `totalFeedbackConfirms`, make sure every place that creates or resets `companionStats` initializes these fields to `0` so achievements/moods/UI never see `undefined` values.
- The stoneage tracking in `fireCompanionObserver` is purely based on `/\bstoneage\b/i` in user text; consider wiring this to the actual stoneage activation path so that stats and achievements can’t be incremented accidentally just by mentioning the word in normal conversation.
- With `getMood` becoming async and doing a filesystem-backed feedback scan, double-check all call sites await it and consider a stricter internal timeout/fallback so `/buddy` and related commands can’t be noticeably delayed if the memory directory is large or slow.
## Individual Comments
### Comment 1
<location path="src/buddy/observer.ts" line_range="337-346" />
<code_context>
+ )
+ onReaction(`${companion.name} ${reply}`)
+
+ // Save feedback reaction memory
+ const currentCompanion = getCompanion()
+ if (currentCompanion) {
+ saveGlobalConfig(curr => ({
+ ...curr,
+ companionMemory: [
+ ...(curr.companionMemory ?? []),
+ {
+ timestamp: Date.now(),
+ trigger: 'feedbackDetected',
+ text: `Feedback ${feedbackResult.type} detectado: ${feedbackResult.message}`,
+ },
+ ],
+ }))
+ }
</code_context>
<issue_to_address>
**suggestion (performance):** Appending every feedback event to `companionMemory` without any cap can bloat the config over time.
Because this array is persisted in the global config, heavy feedback usage will create an ever-growing structure in memory and on disk. Consider enforcing a maximum length (e.g., keep only the last N entries) or moving these logs to a separate append-only storage so the main config stays small and fast to read/write.
Suggested implementation:
```typescript
// ─── Feedback Reactions ───────────────────────────────────────────────
if (feedbackResult?.detected) {
const replies = feedbackResult.type === 'undo' ? FEEDBACK_UNDO_REPLIES : FEEDBACK_CORRECTION_REPLIES
const reply = pickDeterministic(
replies,
`feedback-${feedbackResult.type}-${Date.now()}`,
)
onReaction(`${companion.name} ${reply}`)
// Save feedback reaction memory with a bounded history to avoid unbounded growth
const currentCompanion = getCompanion()
if (currentCompanion) {
saveGlobalConfig(curr => {
const nextMemory = [
...(curr.companionMemory ?? []),
{
timestamp: Date.now(),
trigger: 'feedbackDetected',
text: `Feedback ${feedbackResult.type} detectado: ${feedbackResult.message}`,
},
]
// Keep only the latest N entries to prevent config bloat
const MAX_COMPANION_MEMORY_ENTRIES = 100
const slicedMemory =
nextMemory.length > MAX_COMPANION_MEMORY_ENTRIES
? nextMemory.slice(nextMemory.length - MAX_COMPANION_MEMORY_ENTRIES)
: nextMemory
return {
...curr,
companionMemory: slicedMemory,
}
})
}
```
```typescript
totalErrors: curr.companionStats?.totalErrors ?? 0,
```
If there is already a shared configuration/constants module for limits, you may want to:
1. Extract `MAX_COMPANION_MEMORY_ENTRIES = 100` into that module and import it here instead of defining it inline.
2. Optionally make the cap configurable via the global config schema if you expect to tune this value per-user or environment.
</issue_to_address>
### Comment 2
<location path="docs/buddy.md" line_range="619-628" />
<code_context>
+
+---
+
+## Integracao Stoneage
+
+O Buddy e integrado ao **Stoneage**, o modo de compressao de tokens do OpenClaw. Quando voce ativa o stoneage, o companion:
+
+1. **Reage** com frases tematicas pre-historicas
+2. **Ganha XP** (+0.5 por ativacao)
+3. **Rastreia tokens economizados** em `/buddy stats`
+4. **Desbloqueia conquistas** ao atingir milestones
+
+### Conquistas Stoneage
+
+| Emoji | Conquista | Descricao | Condicao |
+| ----- | ----------------- | --------------------------------- | ------------ |
+| 🪨 | Primeiro Contato | Ativar stoneage pela primeira vez | 1 ativacao |
+| 🔥 | Economia de Fogo | Economizar 1000 tokens estimados | 1000 tokens |
+| 🦣 | Mamute de Ouro | Economizar 10000 tokens estimados | 10000 tokens |
+| ⛏️ | Mestre das Pedras | Ativar stoneage 50 vezes | 25000 tokens |
+
+### Reacoes do Companion
</code_context>
<issue_to_address>
**suggestion (typo):** Corrigir acentuação em português na seção de integração com Stoneage.
Há vários termos sem acentuação nessa seção (`Integracao`, `e integrado`, `compressao`, `Reacoes`, `Descricao`, `Condicao`, `ativacao` etc.). Padronize para o PT-BR com acentos, como no restante da documentação, para manter consistência e evitar aparência de erro de digitação.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| // Save feedback reaction memory | ||
| const currentCompanion = getCompanion() | ||
| if (currentCompanion) { | ||
| saveGlobalConfig(curr => ({ | ||
| ...curr, | ||
| companionMemory: [ | ||
| ...(curr.companionMemory ?? []), | ||
| { | ||
| timestamp: Date.now(), | ||
| trigger: 'feedbackDetected', |
There was a problem hiding this comment.
suggestion (performance): Appending every feedback event to companionMemory without any cap can bloat the config over time.
Because this array is persisted in the global config, heavy feedback usage will create an ever-growing structure in memory and on disk. Consider enforcing a maximum length (e.g., keep only the last N entries) or moving these logs to a separate append-only storage so the main config stays small and fast to read/write.
Suggested implementation:
// ─── Feedback Reactions ───────────────────────────────────────────────
if (feedbackResult?.detected) {
const replies = feedbackResult.type === 'undo' ? FEEDBACK_UNDO_REPLIES : FEEDBACK_CORRECTION_REPLIES
const reply = pickDeterministic(
replies,
`feedback-${feedbackResult.type}-${Date.now()}`,
)
onReaction(`${companion.name} ${reply}`)
// Save feedback reaction memory with a bounded history to avoid unbounded growth
const currentCompanion = getCompanion()
if (currentCompanion) {
saveGlobalConfig(curr => {
const nextMemory = [
...(curr.companionMemory ?? []),
{
timestamp: Date.now(),
trigger: 'feedbackDetected',
text: `Feedback ${feedbackResult.type} detectado: ${feedbackResult.message}`,
},
]
// Keep only the latest N entries to prevent config bloat
const MAX_COMPANION_MEMORY_ENTRIES = 100
const slicedMemory =
nextMemory.length > MAX_COMPANION_MEMORY_ENTRIES
? nextMemory.slice(nextMemory.length - MAX_COMPANION_MEMORY_ENTRIES)
: nextMemory
return {
...curr,
companionMemory: slicedMemory,
}
})
} totalErrors: curr.companionStats?.totalErrors ?? 0,If there is already a shared configuration/constants module for limits, you may want to:
- Extract
MAX_COMPANION_MEMORY_ENTRIES = 100into that module and import it here instead of defining it inline. - Optionally make the cap configurable via the global config schema if you expect to tune this value per-user or environment.
| ## Integracao Stoneage | ||
|
|
||
| O Buddy e integrado ao **Stoneage**, o modo de compressao de tokens do OpenClaw. Quando voce ativa o stoneage, o companion: | ||
|
|
||
| 1. **Reage** com frases tematicas pre-historicas | ||
| 2. **Ganha XP** (+0.5 por ativacao) | ||
| 3. **Rastreia tokens economizados** em `/buddy stats` | ||
| 4. **Desbloqueia conquistas** ao atingir milestones | ||
|
|
||
| ### Conquistas Stoneage |
There was a problem hiding this comment.
suggestion (typo): Corrigir acentuação em português na seção de integração com Stoneage.
Há vários termos sem acentuação nessa seção (Integracao, e integrado, compressao, Reacoes, Descricao, Condicao, ativacao etc.). Padronize para o PT-BR com acentos, como no restante da documentação, para manter consistência e evitar aparência de erro de digitação.
Resumo
Corrige 3 cenários de travamento (hang/freeze) que impediam o uso normal do OpenClaude, especialmente no macOS com Bun runtime.
Problemas corrigidos
1. Deadlock no File Watcher do Bun (
fs.watch)O Bun tem um bug conhecido no
PathWatcherManager(oven-sh/bun#27469): quando um watcher é fechado na thread principal enquanto a thread de File Watcher entrega eventos, ambas as threads ficam presas em__ulock_wait2. Isso era gatilhado pelo chokidar ao monitorar settings e skills.Fix:
usePolling: trueno chokidar quando detectado runtime Bun. Sem FSWatcher = sem deadlock.Arquivos:
src/utils/settings/changeDetector.ts,src/utils/skills/skillChangeDetector.ts2. Travamento no Keychain do macOS
O comando
security find-generic-passwordpodia travar indefinidamente quando o keychain estava bloqueado ou o securityd sobrecarregado. As chamadas não tinham timeout.Fix:
timeout: 1500msem todas as 4 chamadas ao keychain.Arquivos:
src/utils/auth.ts,src/utils/secureStorage/macOsKeychainStorage.ts3. Detecção de IDE com N chamadas síncronas
A função
getVSCodeIDECommandByParentProcess()fazia até 20 chamadas síncronas aopspara caminhar a árvore de processos. Se qualquer chamada demorasse, o event loop travava.Fix: Substituído por
getAncestorCommandsAsync()— uma única chamada shell com timeout de 3s.Arquivos:
src/utils/ide.ts,src/utils/genericProcessUtils.tsDetalhes adicionais
docs/fix-hang-v0.17.0.mdChecklist
npm run build)🤖 Generated with OpenClaude
Summary by Sourcery
Resolve multiple startup and runtime hangs while deepening Buddy integration with feedback and Stoneage features, and updating docs, stats, and achievements for the new behaviors.
New Features:
/buddy statsoutput to track token savings and feedback rule confirmations, and expose richer session information.Bug Fixes:
fs.watchdeadlock by switching chokidar to polling mode under Bun for settings and skill change detection, preventing hangs on large trees or git operations.security-based credential reads and lock checks.pscalls with a single asynchronous ancestor-process scan with a bounded timeout.Enhancements:
wsanduuid) and bump the package version to 0.17.0.Documentation:
docs/fix-hang-v0.17.0.mddocument detailing the three main hang scenarios and their mitigations in this release.Tests: