fix: sync builtin agent prompt updates to sessions (#14304)

### What this PR does

Before this PR:

When Cherry Assistant refreshed its built-in prompt on startup, the
existing agent row was updated directly in `initBuiltinAgent()`.
Existing sessions were not synced, so they could keep stale
`instructions` and continue using an outdated prompt.

After this PR:

Built-in agent prompt updates now go through `updateAgent()`, which
triggers `syncSettingsToSessions()`. Existing sessions that still follow
the agent defaults will receive the updated `instructions`.

Fixes 

### Why we need it and why it was done in this way

Cherry Assistant builds its prompt from `session.instructions`, and
sessions inherit that value from the agent when they are created. The
bug was not in runtime prompt assembly. The bug was that built-in agent
updates bypassed the normal agent-to-session sync path.

This change fixes the problem at the data sync layer instead of adding a
special runtime fallback. That keeps the behavior consistent with the
existing agent/session model and makes the fix minimal enough for a
hotfix.

The following tradeoffs were made:

- This PR keeps the existing "value equality means inherited" sync rule.
- This PR does not add new session-level metadata to distinguish
inherited values from user-customized values.
- This PR fixes the built-in agent update path only and does not broaden
the sync model.

The following alternatives were considered:

- Prefer `agent.instructions` over `session.instructions` at runtime in
`claudecode/index.ts`.
This would mask the symptom, but it would bypass the existing sync model
and add a Cherry Assistant-specific workaround.
- Call `syncSettingsToSessions()` manually after the direct database
update.
This would work, but routing through `updateAgent()` is cleaner because
it reuses the existing update flow and keeps the logic in one place.

### Breaking changes

None.

### Special notes for your reviewer

This is a minimal hotfix. The only functional change is that existing
built-in agent updates now use the normal `updateAgent()` path, so
session sync is applied to inheriting sessions.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [x] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. Check this only
when the PR introduces or changes a user-facing feature or behavior.
- [x] Self-review: I have reviewed my own code (e.g., via
[`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`,
or GitHub UI) before requesting review from others

### Release note

```release-note
Fixed an issue where built-in Cherry Assistant prompt updates did not sync to existing sessions, causing some sessions to keep stale instructions.
```

Co-authored-by: SuYao <sy20010504@gmail.com>
This commit is contained in:
Konv Suu
2026-04-17 03:23:24 +08:00
committed by GitHub
parent 6b5cb0b76b
commit 41554411d1

View File

@@ -216,10 +216,10 @@ export class AgentService extends BaseService {
const workspace = resolvedPaths[0]
const agentConfig = workspace ? await provisionWorkspace(workspace, builtinRole) : undefined
if (agentConfig && (agentConfig.description || agentConfig.instructions)) {
const updateData: Partial<InsertAgentRow> = { updated_at: new Date().toISOString() }
const updateData: UpdateAgentRequest = {}
if (agentConfig.description) updateData.description = agentConfig.description
if (agentConfig.instructions) updateData.instructions = agentConfig.instructions
await database.update(agentsTable).set(updateData).where(eq(agentsTable.id, id))
await this.updateAgent(id, updateData)
}
return { agentId: id }
}