Compare commits

...

2 Commits

Author SHA1 Message Date
Jiayuan Zhang
b0eb028f53 fix(server): use StartedAt instead of CreatedAt for duplicate check
CreatedAt is the enqueue time, not execution start. If a previous task
posted a comment between enqueue and start of the next task, it would
incorrectly suppress the auto-comment for the later task.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:19:51 +08:00
Jiayuan Zhang
dea15d445a fix(server): skip auto-comment when agent already posted during task
In CompleteTask(), check if the agent already posted a comment on the
issue since the task started. If so, skip the automatic output comment
to avoid duplicates. This preserves the fallback for agents that don't
post comments via CLI.

Closes MUL-609

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:08:32 +08:00
3 changed files with 45 additions and 5 deletions

View File

@@ -268,15 +268,23 @@ func (s *TaskService) CompleteTask(ctx context.Context, taskID pgtype.UUID, resu
slog.Info("task completed", "task_id", util.UUIDToString(task.ID), "issue_id", util.UUIDToString(task.IssueID))
// Post agent output as a comment, but only for issue tasks with assignment triggers.
// Post agent output as a comment, but only for assignment-triggered issue tasks
// where the agent did NOT already post a comment during execution.
// Comment-triggered tasks: the agent replies via CLI with --parent, so
// posting here would create a duplicate.
// Chat tasks: no comment posting needed.
if task.IssueID.Valid && !task.TriggerCommentID.Valid {
var payload protocol.TaskCompletedPayload
if err := json.Unmarshal(result, &payload); err == nil {
if payload.Output != "" {
s.createAgentComment(ctx, task.IssueID, task.AgentID, redact.Text(payload.Output), "comment", task.TriggerCommentID)
agentCommented, _ := s.Queries.HasAgentCommentedSince(ctx, db.HasAgentCommentedSinceParams{
IssueID: task.IssueID,
AuthorID: task.AgentID,
Since: task.StartedAt,
})
if !agentCommented {
var payload protocol.TaskCompletedPayload
if err := json.Unmarshal(result, &payload); err == nil {
if payload.Output != "" {
s.createAgentComment(ctx, task.IssueID, task.AgentID, redact.Text(payload.Output), "comment", task.TriggerCommentID)
}
}
}
}

View File

@@ -130,6 +130,29 @@ func (q *Queries) GetCommentInWorkspace(ctx context.Context, arg GetCommentInWor
return i, err
}
const hasAgentCommentedSince = `-- name: HasAgentCommentedSince :one
SELECT EXISTS (
SELECT 1 FROM comment
WHERE issue_id = $1
AND author_type = 'agent'
AND author_id = $2
AND created_at >= $3
) AS commented
`
type HasAgentCommentedSinceParams struct {
IssueID pgtype.UUID `json:"issue_id"`
AuthorID pgtype.UUID `json:"author_id"`
Since pgtype.Timestamptz `json:"since"`
}
func (q *Queries) HasAgentCommentedSince(ctx context.Context, arg HasAgentCommentedSinceParams) (bool, error) {
row := q.db.QueryRow(ctx, hasAgentCommentedSince, arg.IssueID, arg.AuthorID, arg.Since)
var commented bool
err := row.Scan(&commented)
return commented, err
}
const listComments = `-- name: ListComments :many
SELECT id, issue_id, author_type, author_id, content, type, created_at, updated_at, parent_id, workspace_id FROM comment
WHERE issue_id = $1 AND workspace_id = $2

View File

@@ -44,5 +44,14 @@ UPDATE comment SET
WHERE id = $1
RETURNING *;
-- name: HasAgentCommentedSince :one
SELECT EXISTS (
SELECT 1 FROM comment
WHERE issue_id = @issue_id
AND author_type = 'agent'
AND author_id = @author_id
AND created_at >= @since
) AS commented;
-- name: DeleteComment :exec
DELETE FROM comment WHERE id = $1;