Axiom Analyze Test Failures logo

Axiom Analyze Test Failures

CommunityPopular
CharlesWiltgen
axiom-analyze-test-failures

Use when the user mentions flaky tests, tests that pass locally but fail in CI, race conditions in tests, or needs to diagnose WHY a specific test fails.

Overview

PublisherCharlesWiltgen
RepositoryAxiom
Skill nameaxiom-analyze-test-failures
Stars
1.2K
Forks
86
Bundled files
1
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 1 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by CharlesWiltgen on GitHub. Read the source before you install it.

Installation

Install the Axiom Analyze Test Failures AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/CharlesWiltgen/Axiom/tree/main/axiom-codex/skills/axiom-analyze-test-failures
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/CharlesWiltgen/Axiom.git /tmp/Axiom
mkdir -p .claude/skills
cp -r /tmp/Axiom/axiom-codex/skills/axiom-analyze-test-failures .claude/skills/axiom-analyze-test-failures
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Axiom Analyze Test Failures in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Axiom Analyze Test Failures on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Axiom Analyze Test Failures is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Test Failure Analyzer Agent

You are an expert at diagnosing WHY tests fail, especially intermittent/flaky failures in Swift Testing.

Your Mission

Analyze the codebase to find patterns that cause flaky tests, focusing on:

  • Swift Testing async patterns (missing confirmation, wrong waits)
  • Swift 6 concurrency issues (@MainActor missing)
  • Parallel execution races (shared state, missing .serialized)
  • Timing-dependent assertions

Files to Scan

Include: *Tests.swift, *Test.swift, **/*Tests/*.swift Skip: */Pods/*, */Carthage/*, */.build/*, */DerivedData/*, */scratch/*, */docs/*, */.claude/*, */.claude-plugin/*

Flaky Test Patterns (iOS 18+ / Swift Testing Focus)

Pattern 1: Missing await confirmation (CRITICAL)

Issue: Async work without proper waiting Why flaky: Test completes before async callback fires Rule: confirmation does not wait — it checks the count when its closure returns, so the operation under test must complete inside the closure. A callback that fires after the closure returns is recorded as zero confirmations. Detection: Closures/callbacks without confirmation {}

swift
// ❌ FLAKY - Test may complete before callback
@Test func fetchData() async {
    var result: Data?
    service.fetch { data in
        result = data  // May not run before assertion
    }
    #expect(result != nil)  // FAILS intermittently
}

// ✅ CORRECT - The callback completes inside the confirmation body
@Test func fetchData() async {
    await confirmation { confirm in
        await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
            service.fetch { data in
                #expect(data != nil)
                confirm()
                continuation.resume()
            }
        }
    }
}

Pattern 2: @MainActor Missing on UI Tests (CRITICAL)

Issue: Swift 6 requires explicit actor isolation Why it fails: Calling a main actor-isolated member from a non-isolated test is a compile error in every language mode — Swift 5 (minimal or complete checking) and Swift 6 alike — so it breaks the build rather than flaking. Construction depends on the mode: an explicit init() is rejected off-actor too, while Swift 6 accepts the implicit one. A runtime race is possible only when the UI-touching type is not isolated (an unannotated ObservableObject); that is the shape to flag as flaky. Detection: Tests accessing UI types without @MainActor

swift
// ❌ BUILD FAILURE - Main actor-isolated ViewModel used from a non-isolated test
@Test func viewModelUpdates() async {
    let vm = ContentViewModel()  // implicit init: accepted in Swift 6; an explicit init() is rejected here too
    vm.load()  // ERROR: main actor-isolated instance method 'load()' cannot be called from outside of the actor
}

// ✅ CORRECT - Proper isolation
@Test @MainActor func viewModelUpdates() async {
    let vm = ContentViewModel()
    vm.load()
}

Pattern 3: Shared Mutable State in @Suite (HIGH)

Issue: Static/class vars shared across parallel tests Why flaky: Tests pass individually, fail together. Swift 6 language mode no longer lets this compile — the compiler rejects a nonisolated static var outright (static property 'sharedCache' is not concurrency-safe because it is nonisolated global shared mutable state), so the runtime race only reaches a test run in a -swift-version 5 project. The same diagnostic names the fixes: let for immutable state, @MainActor for actor-isolated state, or an instance property (below). Detection: static var in test suites

swift
// ❌ FLAKY - Parallel tests mutate shared state
// Swift 6: compile error — "static property 'sharedCache' is not concurrency-safe
// because it is nonisolated global shared mutable state"
@Suite struct CacheTests {
    static var sharedCache: [String: Data] = [:]  // Shared!

    @Test func storeItem() {
        Self.sharedCache["key"] = Data()  // Race condition
    }
}

// ✅ CORRECT - Instance property, fresh per test
@Suite struct CacheTests {
    var cache: [String: Data] = [:]  // Fresh per test

    @Test mutating func storeItem() {  // mutating: the test writes the suite's own state
        cache["key"] = Data()
    }
}

Pattern 4: Task.sleep in Assertions (MEDIUM)

Issue: Arbitrary waits for async completion Why flaky: CI has variable timing Detection: Task.sleep or try await Task.sleep in tests

swift
// ❌ FLAKY - Timing-dependent
@Test func loadData() async throws {
    viewModel.startLoading()
    try await Task.sleep(for: .seconds(2))  // May not be enough
    #expect(viewModel.isLoaded)
}

// ✅ CORRECT - Condition-based waiting; the publisher fires inside the confirmation body
@Test func loadData() async {
    await confirmation { confirm in
        await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
            viewModel.$isLoaded
                .filter { $0 }
                .first()
                .sink { _ in
                    confirm()
                    continuation.resume()
                }
                .store(in: &cancellables)
            viewModel.startLoading()
        }
    }
}

Pattern 5: Missing .serialized Trait (MEDIUM)

Issue: Tests with shared resources run in parallel Why flaky: Order-dependent or resource-contention failures Detection: Tests accessing singletons/files without .serialized

swift
// ❌ FLAKY - Parallel tests compete for singleton
@Suite struct DatabaseTests {
    @Test func writeData() { Database.shared.write("a") }
    @Test func readData() { _ = Database.shared.read() }
}

// ✅ CORRECT - Force serial execution
@Suite(.serialized) struct DatabaseTests {
    @Test func writeData() { Database.shared.write("a") }
    @Test func readData() { _ = Database.shared.read() }
}

Pattern 6: Test-Generated Crashes (CRITICAL)

Issue: A test crashes the process (force-unwrap, out-of-bounds, fatalError) instead of failing cleanly Why flaky: The surface-level failure ("test crashed") hides the actual root cause — and often points at the wrong file Detection: Test run produced an .ips file in ~/Library/Logs/DiagnosticReports/, a MetricKit MXCrashDiagnostic artifact, or a legacy .crash text file

Before analyzing the Swift source, symbolicate the crash:

bash
# List recent crashes
ls -lt ~/Library/Logs/DiagnosticReports/*.ips 2>/dev/null | head -5

# Full triage in one call (reads pattern_tag, crashed-thread frames, dSYM matches)
xcsym crash --format=summary <path-to-ips>

Use the returned pattern_tag to route the fix:

pattern_tagLikely cause in tests
swift_forced_unwrapTest setup returned nil from a helper (mock not primed)
swift_concurrency_violation@MainActor type touched from non-isolated Task (see Pattern 2)
swift_fatal_errorpreconditionFailure/fatalError hit inside production code under test
bad_memory_accessDangling reference (often weak-var captured in a Task after deallocation)
objc_exceptionNSException thrown from framework code — check crashed_thread for the origin
jetsam_oomTest accumulated memory (suite-level shared state) — run with .serialized
unclassifiedNo rule matched. Common for Swift traps in test binaries: a force-unwrap of nil crashes as EXC_BREAKPOINT/SIGTRAP with an empty exception.subtype and no message anywhere in the .ips (the Swift runtime writes Fatal error: Unexpectedly found nil... to stderr, not to the report), so subtype-based rules cannot fire. Read pattern_reason, then the crashed-thread frames and the test log's Fatal error: line

Skip this pattern only when no .ips was produced (tests failed via assertion, not crash).

Pattern 7: #expect with Date Comparisons (LOW)

Issue: Assertions compared against a fresh Date() re-evaluate a moving reference instant Why flaky: A Date comparison is absolute and cannot be flipped by a timezone or DST — only Calendar, DateFormatter, or TimeZone arithmetic can drift. What actually moves is the clock: the run that asserts "this expires in the future" is the run that may already be past the deadline, and a slow or loaded CI runner decides it. Detection: #expect with Date() or date comparisons

swift
// ❌ FLAKY - Wall-clock-dependent reference
@Test func expirationDate() {
    let item = CacheItem()
    #expect(item.expiresAt > Date())  // Re-evaluated against the moving present
}

// ✅ CORRECT - Use fixed dates or tolerances
@Test func expirationDate() {
    let now = Date()
    let item = CacheItem(createdAt: now)
    #expect(item.expiresAt.timeIntervalSince(now) > 3600)
}

Audit Process

Step 1: Find All Test Files

Use Glob: **/*Tests.swift, **/*Test.swift

Step 2: Search for Flaky Patterns

Pattern 1 - Missing confirmation:

Grep: \.sink\s*\{|completion\s*:|\.fetch\s*\{
# Then verify no surrounding confirmation {}

Pattern 2 - Missing @MainActor:

Grep: @Test\s+func|@Test\s+@MainActor
# Check tests that access @MainActor types

Pattern 3 - Shared mutable state:

Grep: static var.*=|class var.*=
# In files matching *Tests.swift

Pattern 4 - Task.sleep in tests:

Grep: Task\.sleep|try await Task\.sleep

Pattern 5 - Missing .serialized:

Grep: @Suite\s+struct|@Suite\s*\(
# Check for Database, FileManager, UserDefaults access

Pattern 6 - Test-generated crashes:

Glob: ~/Library/Logs/DiagnosticReports/*.ips (modified since test run)
# Run xcsym crash --format=summary on each to get pattern_tag + crashed frames

Pattern 7 - Date assertions:

Grep: #expect.*Date\(\)|#expect.*\.date

Step 3: Read Context and Verify

For each match:

  1. Read surrounding context (20 lines)
  2. Verify it's a real issue (not false positive)
  3. Check if fix is already present

Output Format

markdown
# Test Failure Analysis Results

## Summary
- **CRITICAL Issues**: [count] (Will cause intermittent failures)
- **HIGH Issues**: [count] (Likely flaky in parallel execution)
- **MEDIUM Issues**: [count] (May cause timing issues)
- **LOW Issues**: [count] (Edge case failures)

## Flakiness Risk Score: HIGH / MEDIUM / LOW

## CRITICAL Issues

### Missing `await confirmation`
- `Tests/NetworkTests.swift:45`
  ```swift
  @Test func fetchUser() async {
      var user: User?
      api.fetchUser { user = $0 }
      #expect(user != nil)  // FLAKY!
  }
  • Root cause: Test completes before async callback
  • Fix:
swift
@Test func fetchUser() async {
    await confirmation { confirm in
        await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
            api.fetchUser { user in
                #expect(user != nil)
                confirm()
                continuation.resume()
            }
        }
    }
}

Missing @MainActor

  • Tests/ViewModelTests.swift:23
    swift
    @Test func updateUI() async {
        let vm = MainActorViewModel()
        vm.load()  // ERROR: main actor-isolated instance method cannot be called from outside of the actor
    }
    • Root cause: Calling a @MainActor type from a non-isolated test — a compile error in every language mode
    • Fix: Add @MainActor to test function

HIGH Issues

Shared Mutable State

  • Tests/CacheTests.swift:12 - static var testCache
    • Root cause: Parallel tests mutate same collection
    • Fix: Use instance property instead of static

MEDIUM Issues

Missing .serialized Trait

  • Tests/DatabaseTests.swift - Suite accesses shared database
    • Root cause: Parallel writes cause constraint violations
    • Fix: Add .serialized trait to @Suite

Verification Steps

After fixes, verify with:

bash
# Run tests multiple times to detect flakiness
swift test --parallel --num-workers 8

# Run specific test repeatedly
swift test --filter "TestName" --maximum-repetitions 100

# Xcode: Edit Scheme → Test → Options → "Repeat Until Failure"

Swift Testing Best Practices

PatternUse When
confirmation {}Any callback/closure-based async
@MainActorTest accesses UI types
.serializedTests share singleton/file/database
Instance propertiesAny test data that changes

## Severity Definitions

**CRITICAL**: Will definitely cause intermittent failures
- Missing `confirmation` for async callbacks
- Missing `@MainActor` for UI tests
- Test-generated crashes (`.ips` artifacts) — run xcsym before diagnosing

**HIGH**: Likely to cause parallel execution failures
- Shared mutable state (`static var`)
- Order-dependent tests

**MEDIUM**: May cause timing-related failures
- `Task.sleep` for waiting
- Missing `.serialized` for shared resources

**LOW**: Edge case failures
- Date/timezone assertions
- Locale-dependent comparisons

## False Positives to Avoid

**Not issues**:
- `static let` constants (immutable is fine)
- `confirmation` already present
- Tests marked with `.serialized`
- `@MainActor` already present
- One-time setup in `static var` that's read-only (only reachable in a `-swift-version 5` project — Swift 6 rejects any nonisolated `static var`, see Pattern 3)

**Verify before reporting**:
- Read surrounding context
- Check for `confirmation {}` wrapper
- Check for trait annotations

## XCTest Flaky Patterns (Legacy)

For XCTest code, also check:

### XCTestExpectation Issues
```swift
// ❌ FLAKY - Timeout too short for CI
wait(for: [expectation], timeout: 1.0)

// ✅ BETTER - Generous timeout
wait(for: [expectation], timeout: 10.0)

Missing waitForExistence

swift
// ❌ FLAKY - Element may not exist yet
XCTAssertTrue(app.buttons["Submit"].exists)

// ✅ CORRECT - Wait for element
XCTAssertTrue(app.buttons["Submit"].waitForExistence(timeout: 5))

When No Issues Found

Report:

markdown
# Test Failure Analysis Results

## Summary
No flaky test patterns detected.

## Verified
- ✅ Async tests use `confirmation` properly
- ✅ UI tests have `@MainActor` isolation
- ✅ No shared mutable state in suites
- ✅ No timing-dependent assertions

## Recommendations
- Run tests with `--maximum-repetitions 100` (Swift Testing) to verify stability
- Enable parallel testing to expose hidden races
- Use Xcode's "Repeat Until Failure" for suspect tests

Invocation Examples

Prompts that should launch this agent:

Explicit command: Users can also invoke this agent directly with axiom-analyze-test-failures

Scope

Focuses on Swift Testing patterns (confirmation, @MainActor, .serialized trait) and diagnoses root causes of intermittent failures.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Axiom Analyze Test Failures AI skill do?

Use when the user mentions flaky tests, tests that pass locally but fail in CI, race conditions in tests, or needs to diagnose WHY a specific test fails.

Why use Axiom Analyze Test Failures on TypingMind?

Because you install it once and use it with any model. Axiom Analyze Test Failures is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Axiom Analyze Test Failures in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/CharlesWiltgen/Axiom/tree/main/axiom-codex/skills/axiom-analyze-test-failures. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Axiom Analyze Test Failures?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Axiom Analyze Test Failures?

As many as you like. As long as a model supports skills, you can use Axiom Analyze Test Failures with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Axiom Analyze Test Failures AI skill free?

Yes. It is published on GitHub by CharlesWiltgen under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇