Repository navigation
Conversation
`incident comment` already re-read every target's timeline after writing, to
confirm an entry with the exact sent text was present. It then discarded what
it found and printed only a count, so learning what actually landed — the
stored body, the author the server recorded, the timestamp — still took a
second `incident timeline` call plus ad-hoc feed parsing in every caller.
The read-back now returns the entry it located and the command emits it: with
--json or --output-format toon, one object per incident in argument order
carrying incident_id, comment, creator_id and created_at. Table mode still
prints `Commented on N incident(s).`, and the failure path is unchanged — an
unconfirmed incident anywhere in the batch still exits non-zero with the same
anti-retry guidance, and prints no partial array.
The shape is a top-level array at every batch size, so a caller never has to
branch on arity. POST /incident/comment answers with an empty data envelope
and feed entries carry no per-entry id, so those four fields are the whole of
what any endpoint can report about a stored comment; there is no comment id to
return.
Behavior change for structured output: this command used to print
{"message": "Commented on N incident(s)."} under --json. A script reading
.message must read the entries instead.
…ns by member id Two gaps in `incident comment`'s read-after-write check, both of which still left a caller reading the timeline afterwards. The result line said only that a comment was sent. The command had verified it on every target's timeline, but nothing in the output said so, and an exit code is not visible evidence — so a reader had no way to tell from the output that the check ran, and went and read the timeline anyway. The line now names the confirmation: `Commented on 2 incident(s); confirmed on each timeline.` It is the first and only line of default output, so it survives a `| head`, and the per-entry detail stays in --json / --output-format toon rather than becoming extra lines above it. Mentions were compared by display label, which the server does not keep. A mention written as [@Label](flashduty://ref/member/<id>) has its label replaced with the member's canonical name on write, and refreshed from current member names again when the timeline is served — both documented on POST /incident/comment, whose `comment` field is also specified as counted "after @mention normalization". Comparing stored text to sent text literally therefore reported a fully successful write as unconfirmed whenever the caller's label was not already the canonical name, and a reported failure is exactly what sends a caller back to the timeline by hand. Each mention is now reduced to a label-free form on both sides before comparing, so a rewritten label matches while a different member id does not, and no other difference in the body is forgiven. The label pattern accepts backslash escapes, because the canonical name arrives with its ASCII punctuation escaped and a name containing "]" would otherwise cut the match short. The returned entry still carries the stored text, canonical labels included, which is what a caller asking what landed wants to read. No CreatedAt floor was added and the older-identical-text limitation is unchanged; canonicalization widens it by one mention label.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
incident commentposts to up to 100 incidents in one batch POST, then re-reads every target's/incident/feedto confirm ani_commentry matching the sent text is present. That read-back already exists. Two things about it stopped a caller from trusting it, and both end the same way — the caller reads the timeline anyway, which is the round trip the read-back was supposed to remove.1. The entry it verified was thrown away
The read-back reduced the entry it found to a bool, and the command printed a count. So it could answer "did it land?" and nothing else; learning what landed took a second
flashduty incident timeline <id>call plus feed parsing reinvented in every caller.It now returns the entry. With
--jsonor--output-format toon, one object per incident in argument order:[ { "incident_id": "69da451ef77b1b51f40e83ee", "comment": "Root cause identified: DB failover.\nFix deploying.", "creator_id": 2476444212131, "created_at": "2026-11-10T00:00:00+08:00" } ]POST /incident/commentanswers with an emptydataenvelope — no id, no echo — and feed entries carry no per-entry id (IncidentFeedItemhasaccount_id,created_at,creator_id,deleted_at,detail,ref_id,type,updated_at, none of which identifies the comment). There is no comment id to return.incident_idis the queried id, notitem.ref_id, which is documented as the source alert or incident the entry references.2. The output never said the verification happened
The result line said only that a comment was sent. The exit code already carried the verdict, but an exit code is not visible evidence: nothing in the output told a reader the command had confirmed the comment on the timeline. The line now says it:
That is the first and only line of default output, so it survives a
| head. The per-entry detail stays in--json/--output-format toonrather than becoming extra lines above the verdict.3. Mentions were compared by a label the server does not keep
A member mention is written
[@Label](flashduty://ref/member/<member-id>).POST /incident/commentspecifies that "the server rewrites each mention's display label to the member's canonical name", and itscommentfield is specified as "at most 1024 characters (counted after @mention normalization)". The timeline refreshes the label again from current member names when it serves the entry. So the stored text of a comment written perfectly is not the text that was sent.The read-back compared the two literally, so any comment carrying a mention whose label was not already the canonical name was reported unconfirmed on a write that fully succeeded — and a reported failure is what sends a caller back to the timeline by hand.
Each mention is now reduced to a label-free form on both sides before comparing. Scope of the change:
[@Jane]→[@Jane Doe], same member id[@Jane\]\*\_Doe]The label pattern accepts backslash escapes because the canonical name arrives with its ASCII punctuation escaped, so a name containing
]would otherwise cut the match short. The returned entry still carries the stored text, canonical labels included — which is where a caller reads back the real names.Unchanged by design: no
CreatedAtfloor (the comment on the finder explains why clock ordering stays out of this), and the documented older-identical-text limitation stands, widened by one mention label.Where the change lives
The curated command in
internal/cli/incident.go, which wins thecommentname over its generated twin at registration. Both generators (cligen, the skill-card generator) were re-run: zero drift, so regeneration cannot wipe this.Behavior change
Structured output for this command used to be
{"message": "Commented on N incident(s)."}. A script reading.messagemust read the entries instead. The default-output line changed text as shown above. Nothing in this repo consumed either shape.Tests
In
internal/cli/command_test.go, against the existing go-flashduty stub:created_atparses as RFC3339 back to the feed entry's instantContainscannot catch a line pushed down by output added above itmakeis green: gofmt, gci,golangci-lint runwith 0 issues,go test -race ./...across all packages, build.make check-cardsOK.The
e2e/suite is behind//go:build e2eand needs a live app key; it was compile-checked (go vet -tags e2e ./e2e/) but not run, and no comment was posted to any real incident. Both output shapes, the mention rewrite, the| headbehavior and the non-zero failure path were exercised end to end against a local stub HTTP server with the built binary.Docs
skills/flashduty/reference/incident.mdrecords the new output line, the array of entries, and the mention comparison rule.