add-background-service

Create BackgroundService implementations for scheduled or polling tasks (project)

181 stars

Best use case

add-background-service is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Create BackgroundService implementations for scheduled or polling tasks (project)

Teams using add-background-service should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/add-background-service/SKILL.md --create-dirs "https://raw.githubusercontent.com/majiayu000/claude-skill-registry/main/skills/data/add-background-service/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/add-background-service/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How add-background-service Compares

Feature / Agentadd-background-serviceStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Create BackgroundService implementations for scheduled or polling tasks (project)

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Add Background Service Skill

Create `BackgroundService` implementations for scheduled or polling tasks in NovaTune.

## Project Context

- Service location: `src/NovaTuneApp/NovaTuneApp.ApiService/Infrastructure/` or worker projects
- Base class: `Microsoft.Extensions.Hosting.BackgroundService`
- Pattern: Polling loop with configurable interval

## Steps

### 1. Create Options Class

Location: `src/NovaTuneApp/NovaTuneApp.ApiService/Configuration/{ServiceName}Options.cs`

```csharp
namespace NovaTuneApp.ApiService.Configuration;

public class PhysicalDeletionOptions
{
    public const string SectionName = "PhysicalDeletion";

    /// <summary>
    /// Interval between polling cycles.
    /// Default: 5 minutes.
    /// </summary>
    public TimeSpan PollingInterval { get; set; } = TimeSpan.FromMinutes(5);

    /// <summary>
    /// Maximum items to process per cycle.
    /// Default: 50.
    /// </summary>
    public int BatchSize { get; set; } = 50;

    /// <summary>
    /// Whether the service is enabled.
    /// Default: true.
    /// </summary>
    public bool Enabled { get; set; } = true;
}
```

### 2. Create Background Service

Location: `src/NovaTuneApp/NovaTuneApp.ApiService/Infrastructure/Services/{ServiceName}Service.cs`

```csharp
using Microsoft.Extensions.Options;

namespace NovaTuneApp.ApiService.Infrastructure.Services;

/// <summary>
/// Background service that processes physical deletions for soft-deleted tracks.
/// </summary>
public class PhysicalDeletionService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly IOptions<PhysicalDeletionOptions> _options;
    private readonly ILogger<PhysicalDeletionService> _logger;

    public PhysicalDeletionService(
        IServiceProvider serviceProvider,
        IOptions<PhysicalDeletionOptions> options,
        ILogger<PhysicalDeletionService> logger)
    {
        _serviceProvider = serviceProvider;
        _options = options;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        if (!_options.Value.Enabled)
        {
            _logger.LogInformation("Physical deletion service is disabled");
            return;
        }

        _logger.LogInformation(
            "Physical deletion service starting with {Interval} interval",
            _options.Value.PollingInterval);

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await ProcessBatchAsync(stoppingToken);
            }
            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
            {
                break;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing physical deletions");
            }

            await Task.Delay(_options.Value.PollingInterval, stoppingToken);
        }

        _logger.LogInformation("Physical deletion service stopped");
    }

    private async Task ProcessBatchAsync(CancellationToken ct)
    {
        using var scope = _serviceProvider.CreateScope();
        var session = scope.ServiceProvider.GetRequiredService<IAsyncDocumentSession>();
        var storageService = scope.ServiceProvider.GetRequiredService<IStorageService>();

        var itemsToProcess = await session
            .Query<Track, Tracks_ByScheduledDeletion>()
            .Where(t => t.Status == TrackStatus.Deleted
                     && t.ScheduledDeletionAt <= DateTimeOffset.UtcNow)
            .Take(_options.Value.BatchSize)
            .ToListAsync(ct);

        if (itemsToProcess.Count == 0)
        {
            _logger.LogDebug("No items to process");
            return;
        }

        _logger.LogInformation("Processing {Count} items for physical deletion", itemsToProcess.Count);

        foreach (var item in itemsToProcess)
        {
            try
            {
                await ProcessItemAsync(item, session, storageService, ct);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to process item {ItemId}", item.Id);
                // Continue with next item; will retry on next poll
            }
        }
    }

    private async Task ProcessItemAsync(
        Track track,
        IAsyncDocumentSession session,
        IStorageService storageService,
        CancellationToken ct)
    {
        // Delete storage objects
        await storageService.DeleteObjectAsync(track.ObjectKey, ct);

        if (track.WaveformObjectKey is not null)
        {
            await storageService.DeleteObjectAsync(track.WaveformObjectKey, ct);
        }

        // Delete document
        session.Delete(track);
        await session.SaveChangesAsync(ct);

        _logger.LogInformation(
            "Physically deleted track {TrackId} for user {UserId}",
            track.TrackId, track.UserId);
    }
}
```

### 3. Register Service

In `Program.cs`:

```csharp
// Register options
builder.Services.Configure<PhysicalDeletionOptions>(
    builder.Configuration.GetSection(PhysicalDeletionOptions.SectionName));

// Register hosted service
builder.Services.AddHostedService<PhysicalDeletionService>();
```

### 4. Add Configuration

In `appsettings.json`:

```json
{
  "PhysicalDeletion": {
    "PollingInterval": "00:05:00",
    "BatchSize": 50,
    "Enabled": true
  }
}
```

## Patterns

### Simple Polling Service

```csharp
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        await DoWorkAsync(stoppingToken);
        await Task.Delay(_interval, stoppingToken);
    }
}
```

### Service with Startup Delay

```csharp
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    // Let the app start up first
    await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);

    while (!stoppingToken.IsCancellationRequested)
    {
        await DoWorkAsync(stoppingToken);
        await Task.Delay(_interval, stoppingToken);
    }
}
```

### Service with Retry Logic

```csharp
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    const int maxRetries = 5;

    while (!stoppingToken.IsCancellationRequested)
    {
        var retryCount = 0;
        var success = false;

        while (!success && retryCount < maxRetries)
        {
            try
            {
                await DoWorkAsync(stoppingToken);
                success = true;
            }
            catch (Exception ex) when (retryCount < maxRetries - 1)
            {
                retryCount++;
                _logger.LogWarning(ex, "Retry {Attempt}/{Max}", retryCount, maxRetries);
                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)), stoppingToken);
            }
        }

        await Task.Delay(_interval, stoppingToken);
    }
}
```

## Best Practices

1. **Use scoped services** - Create scope for each batch/iteration
2. **Handle cancellation** - Always check `stoppingToken`
3. **Log appropriately** - Info for start/stop, Debug for iterations
4. **Configure intervals** - Use options pattern for configuration
5. **Process in batches** - Avoid loading too many items at once
6. **Continue on item failure** - Don't fail the entire batch

## Testing

```csharp
[Fact]
public async Task Service_Should_ProcessItemsInBatches()
{
    // Arrange
    var options = Options.Create(new PhysicalDeletionOptions
    {
        BatchSize = 10,
        PollingInterval = TimeSpan.FromMilliseconds(100)
    });

    var service = new PhysicalDeletionService(
        _serviceProvider,
        options,
        _logger);

    // Act
    using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
    await service.StartAsync(cts.Token);
    await Task.Delay(500);
    await service.StopAsync(CancellationToken.None);

    // Assert
    // Verify items were processed
}
```

Related Skills

add-service

181
from majiayu000/claude-skill-registry

새 API 서비스를 생성합니다. service + hook + type 세트를 함께 생성합니다. 사용법: /add-service EntityName

add-k8s-service

181
from majiayu000/claude-skill-registry

往 Kubernetes 集群添加普通服务。当用户请求部署新应用、添加新服务到集群时使用此技能。

acc-create-domain-service

181
from majiayu000/claude-skill-registry

Generates DDD Domain Services for PHP 8.5. Creates stateless services for business logic that doesn't belong to entities or value objects. Includes unit tests.

abp-service-patterns

181
from majiayu000/claude-skill-registry

ABP Framework application layer patterns including AppServices, DTOs, Mapperly mapping, Unit of Work, and common patterns like Filter DTOs and ResponseModel. Use when: (1) creating AppServices, (2) mapping DTOs with Mapperly, (3) implementing list filtering, (4) wrapping API responses.

whisper-transcribe

159
from majiayu000/claude-skill-registry

Transcribes audio and video files to text using OpenAI's Whisper CLI, enhanced with contextual grounding from local markdown files for improved accuracy.

Media Processing

modal-deployment

159
from majiayu000/claude-skill-registry

Run Python code in the cloud with serverless containers, GPUs, and autoscaling using Modal. This skill enables agents to generate code for deploying ML models, running batch jobs, serving APIs, and scaling compute-intensive workloads.

DevOps & Infrastructure

ux

159
from majiayu000/claude-skill-registry

This AI agent skill provides comprehensive guidance for creating professional and insightful User Experience (UX) designs, covering user research, information architecture, interaction design, visual guidance, and usability evaluation. It aims to produce actionable, user-centered solutions that avoid generic AI aesthetics.

UX Design & StrategyClaude

vly-money

159
from majiayu000/claude-skill-registry

Generate crypto payment links for supported tokens and networks, manage access to X402 payment-protected content, and provide direct access to the vly.money wallet interface.

Fintech & CryptoClaude

astro

159
from majiayu000/claude-skill-registry

This skill provides essential Astro framework patterns, focusing on server-side rendering (SSR), static site generation (SSG), middleware, and TypeScript best practices. It helps AI agents implement secure authentication, manage API routes, and debug rendering behaviors within Astro projects.

Coding & Development

lets-go-rss

159
from majiayu000/claude-skill-registry

A lightweight, full-platform RSS subscription manager that aggregates content from YouTube, Vimeo, Behance, Twitter/X, and Chinese platforms like Bilibili, Weibo, and Douyin, featuring deduplication and AI smart classification.

Content & Documentation

ontopo

159
from majiayu000/claude-skill-registry

An AI agent skill to search for Israeli restaurants, check table availability, view menus, and retrieve booking links via the Ontopo platform, acting as an unofficial interface to its data.

General Utilities

thor-skills

159
from majiayu000/claude-skill-registry

An entry point and router for AI agents to manage various THOR-related cybersecurity tasks, including running scans, analyzing logs, troubleshooting, and maintenance.

SecurityClaude