The maintenance team started with a noisy problem. A C++ service emitted more than four hundred compiler warnings during a nightly build. Most warnings were real. Very few mattered. The team's first response was to send every warning to a model for an explanation. That created a second problem. The remote call queue grew. The same ten warnings returned every night with slightly different text. The team could not tell which explanations were new.
The group had access to MonkeyCode's free model access and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Instead of sending every warning, they built a two-layer local gate. The first layer was a rule table. It matched known benign patterns. The second layer was a fingerprint window. It dropped warnings that had already been classified in the last few days. Only new or high-signal warnings reached the remote model. The free server option was not used as a retry mechanism. It became an audit store for rejected and unanswered warnings.
The process
- Normalize the warning into a fixed shape: file, line, checker name, and message text.
- Score the warning with a small local heuristic. A score of -2 means strong evidence of noise. A score of +2 means strong evidence of a real defect.
- Compute a stable fingerprint from the normalized fields.
- If the score is low and the fingerprint was seen before, drop the warning.
- If the score is positive or the fingerprint is new, send the warning for remote review.
- If the remote call fails or the local budget is exhausted, write an audit event to the free server. Do not retry.
#include <chrono>
#include <cstdint>
#include <string>
#include <unordered_map>
struct WarningRecord {
std::string file;
int line{0};
std::string checker;
std::string message;
};
class LocalTriage {
public:
enum class Verdict {
LocalBenign,
RemoteReview,
KnownHighSeverity
};
explicit LocalTriage(std::chrono::seconds window)
: window_(window) {}
Verdict Decide(const WarningRecord& record,
std::chrono::steady_clock::time_point now) {
const int score = Score(record);
const uint64_t fp = Fingerprint(record);
if (IsKnownHighSeverity(record)) {
return Verdict::KnownHighSeverity;
}
const auto it = seen_.find(fp);
if (it != seen_.end() && score <= 0) {
return Verdict::LocalBenign;
}
if (it == seen_.end() || score > 0) {
seen_[fp] = now;
return Verdict::RemoteReview;
}
return Verdict::LocalBenign;
}
private:
static bool IsKnownHighSeverity(const WarningRecord& r) {
return r.checker == "clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling" ||
r.checker == "clang-analyzer-core.uninitialized.Branch" ||
r.checker == "clang-analyzer-cplusplus.NewDeleteLeaks";
}
static int Score(const WarningRecord& r) {
if (r.message.find("unused parameter") != std::string::npos) return -2;
if (r.message.find("enumeration value not handled") != std::string::npos) return -2;
if (r.message.find("possible null pointer") != std::string::npos) return 1;
if (r.message.find("use after free") != std::string::npos) return 2;
if (r.message.find("race condition") != std::string::npos) return 2;
return 0;
}
static uint64_t Fingerprint(const WarningRecord& r) {
uint64_t h = 1469598103934665603ULL;
const auto mix = [&h](char c) {
h ^= static_cast<uint8_t>(c);
h *= 1099511628211ULL;
};
for (char c : r.file) mix(c);
mix(':');
for (char c : std::to_string(r.line)) mix(c);
mix(':');
for (char c : r.checker) mix(c);
mix(':');
for (char c : r.message) mix(c);
return h;
}
std::chrono::seconds window_;
std::unordered_map<uint64_t, std::chrono::steady_clock::time_point> seen_;
};
The local heuristic is intentionally small. It does not try to replace the model. It only removes the cases where the model repeated itself. The high-severity branch still sends a known dangerous pattern to remote review. Recurrence is not always duplication. A new call site can produce the same checker name with a different file and line, so the fingerprint changes.
#include <chrono>
#include <string>
#include <tuple>
#include <vector>
class RemoteReviewGate {
public:
explicit RemoteReviewGate(size_t daily_budget)
: daily_budget_(daily_budget) {}
bool ShouldCall(std::chrono::system_clock::time_point now) {
if (!same_day(now, day_start_)) {
day_start_ = now;
used_ = 0;
}
return used_ < daily_budget_;
}
void RecordCall() {
++used_;
}
void PostAudit(const WarningRecord& record, const std::string& result) {
audit_queue_.push_back({record.file, record.line, record.checker, result});
}
private:
static bool same_day(std::chrono::system_clock::time_point a,
std::chrono::system_clock::time_point b) {
return std::chrono::duration_cast<std::chrono::hours>(a.time_since_epoch()).count() / 24 ==
std::chrono::duration_cast<std::chrono::hours>(b.time_since_epoch()).count() / 24;
}
size_t daily_budget_;
std::chrono::system_clock::time_point day_start_{};
size_t used_{0};
std::vector<std::tuple<std::string, int, std::string, std::string>> audit_queue_;
};
The orchestration is deliberately boring. It tries the local gate first. It checks a local daily budget second. It calls the remote model third. If the call returns nothing, it posts an audit event and moves on. This avoids the retry loop that made previous integrations fragile.
LocalTriage triage(std::chrono::minutes(72 * 60));
RemoteReviewGate remote(120); // local budget, not a product quota
for (const auto& warn : WarningsFromBuildLog("build.log")) {
auto verdict = triage.Decide(warn, std::chrono::steady_clock::now());
if (verdict == LocalTriage::Verdict::LocalBenign) {
continue;
}
if (!remote.ShouldCall(std::chrono::system_clock::now())) {
remote.PostAudit(warn, "budget-exhausted");
continue;
}
auto maybe = CallFreeModelEndpoint(warn);
if (!maybe) {
remote.PostAudit(warn, "endpoint-unavailable");
continue;
}
remote.RecordCall();
remote.PostAudit(warn, *maybe);
}
WarningsFromBuildLog and CallFreeModelEndpoint are placeholders. The first would parse the compiler output. The second would call the model endpoint. The interesting logic is the gate, not the transport.
| Local score | Seen before? | Action |
|---|---|---|
| -2 | yes | drop |
| -2 | no | drop |
| 0 | no | remote review |
| 0 | yes | drop |
| +1 or +2 | any | remote review |
| high severity pattern | any | remote review regardless |
The table has one deliberate edge. A new warning with score 0 goes to remote review. An old warning with score 1 or 2 also goes to remote review. The team wanted the model to see recurrence for anything that looked even slightly dangerous.
What the free server actually did
Every audit event landed in a small key-value record. The file, line, checker, and result were stored. When the model endpoint returned an empty body or a transport error, the audit row still existed. A separate replay job could later pull the stored warning and test it again. None of that required retrying the live endpoint during the build. The free server option became a durable side channel, not a second model caller.
This structure helped in one common failure case. The endpoint returned 200 with an empty body for a burst of warnings. The code did not loop. It wrote each missing result to the audit queue. The next replay job picked them up. The build stayed fast. The missing model explanations did not block the compiler output.
Limitations
The local heuristic is incomplete. A warning that does not match the rule table can be benign or dangerous, and the gate will still send it remote once. That is acceptable for this case because the budget is sized for review, not for full automation.
The fingerprint is also lossy. It treats the same warning on another line as a new warning. That can waste calls on repeated patterns. The team accepted that trade-off because line-level deduplication was safer than message-level deduplication. A message-level match could hide a new instance of an old bug.
The daily budget creates a blind spot. If a bad night produces more high-signal warnings than the budget allows, the later warnings go to the audit queue without a model explanation. The next replay job may pick them up, but there is no guarantee. The system is a filter, not a queue with delivery semantics.
The model output is review input. It can be wrong, and it can be confidently wrong. The team did not apply any model-generated fix directly. Every remote classification went through the same human review as the local rule changes.
Who should not use this approach
Do not use this gate in a safety-critical or regulated workflow. Do not use it as a replacement for a real static analyzer. Do not use it if the team cannot inspect and update the local rule table. A silent local rule is worse than a noisy remote call. If the team cannot explain why a warning was dropped, the gate should be removed.
Before you wire a remote call into a noisy tool, replay a week of old warnings through the gate and count what actually changes. If the gate removes the same ten benign warnings and keeps the one dangerous warning, it is doing its job. If it hides a warning the team would have acted on, the rules need editing before the model ever gets involved.
Top comments (0)