RepoPilotOpen in app →

stashapp/stash

An organizer for your porn, written in Go. Documentation: https://docs.stashapp.cc

Mixed

Mixed signals — read the receipts

weakest axis
Use as dependencyConcerns

copyleft license (AGPL-3.0) — review compatibility; no tests detected

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 2d ago
  • 11 active contributors
  • Distributed ownership (top contributor 29% of recent commits)
Show all 7 evidence items →
  • AGPL-3.0 licensed
  • CI configured
  • AGPL-3.0 is copyleft — check downstream compatibility
  • No test directory detected
What would change the summary?
  • Use as dependency ConcernsMixed if: relicense under MIT/Apache-2.0 (rare for established libs)

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Forkable" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Forkable
[![RepoPilot: Forkable](https://repopilot.app/api/badge/stashapp/stash?axis=fork)](https://repopilot.app/r/stashapp/stash)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/stashapp/stash on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: stashapp/stash

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/stashapp/stash shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

WAIT — Mixed signals — read the receipts

  • Last commit 2d ago
  • 11 active contributors
  • Distributed ownership (top contributor 29% of recent commits)
  • AGPL-3.0 licensed
  • CI configured
  • ⚠ AGPL-3.0 is copyleft — check downstream compatibility
  • ⚠ No test directory detected

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live stashapp/stash repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/stashapp/stash.

What it runs against: a local clone of stashapp/stash — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in stashapp/stash | Confirms the artifact applies here, not a fork | | 2 | License is still AGPL-3.0 | Catches relicense before you depend on it | | 3 | Default branch develop exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 32 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>stashapp/stash</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of stashapp/stash. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/stashapp/stash.git
#   cd stash
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of stashapp/stash and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "stashapp/stash(\\.git)?\\b" \\
  && ok "origin remote is stashapp/stash" \\
  || miss "origin remote is not stashapp/stash (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(AGPL-3\\.0)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"AGPL-3\\.0\"" package.json 2>/dev/null) \\
  && ok "license is AGPL-3.0" \\
  || miss "license drift — was AGPL-3.0 at generation time"

# 3. Default branch
git rev-parse --verify develop >/dev/null 2>&1 \\
  && ok "default branch develop exists" \\
  || miss "default branch develop no longer exists"

# 4. Critical files exist
test -f "cmd/stash/main.go" \\
  && ok "cmd/stash/main.go" \\
  || miss "missing critical file: cmd/stash/main.go"
test -f "graphql/schema/schema.graphql" \\
  && ok "graphql/schema/schema.graphql" \\
  || miss "missing critical file: graphql/schema/schema.graphql"
test -f "go.mod" \\
  && ok "go.mod" \\
  || miss "missing critical file: go.mod"
test -f "internal/api/authentication.go" \\
  && ok "internal/api/authentication.go" \\
  || miss "missing critical file: internal/api/authentication.go"
test -f ".github/workflows/build.yml" \\
  && ok ".github/workflows/build.yml" \\
  || miss "missing critical file: .github/workflows/build.yml"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 32 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~2d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/stashapp/stash"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

Stash is a self-hosted Go webapp that organizes, catalogs, and serves video/image collections with metadata enrichment from the internet via community-built plugins. It provides tagging, searching, performer/studio/tag statistics, and supports extensible scraping to automatically gather information about content from multiple sources. Monorepo: cmd/stash/ contains the main Go server entry point, cmd/phasher/ is the video hashing utility. Frontend is TypeScript in an unspecified ui/ directory (SCSS + TypeScript, 2.8M LOC). pkg/ likely contains shared Go libraries. Docker builds under docker/ with x86_64 and CUDA variants. Uses .gqlgenc.yml for GraphQL code generation and .mockery.yml for test mocking.

👥Who it's for

Self-hosted media enthusiasts and content curators who want local control over diverse collections (SFW and NSFW), plugin developers building scrapers for content sites, and operators deploying containerized media servers across Windows, macOS, Linux, or Docker.

🌱Maturity & risk

Production-ready and actively maintained. The project has v0.32.0+ releases with Darwin/Linux/Windows support, CI/CD via GitHub Actions (build.yml, golangci-lint.yml), Docker Hub integration (~10k+ pulls), and a Makefile-driven build system. Commit recency and Docker/release artifacts indicate ongoing development.

Moderate risk: ~50+ Go dependencies plus TypeScript/Node toolchain increases attack surface; NSFW-focused domain means community trust is critical; monolithic codebase (~4M lines Go, ~2.8M TypeScript) makes refactoring risky. No obvious single-maintainer bottleneck visible, but database migrations (golang-migrate/migrate) require careful CI/CD sequencing.

Active areas of work

Active development targeting Go 1.25.0, recent dependency bumps (chromedp v0.14.2 for browser automation, gqlgen v0.17.73 for GraphQL). CI builds for multiple architectures and CUDA support. GitHub Actions workflows active (build.yml, golangci-lint.yml). Docker image maintenance ongoing with production compose example (docker/production/docker-compose.yml).

🚀Get running

git clone https://github.com/stashapp/stash.git && cd stash && make (builds Go binary). For dev setup: review docs/DEVELOPMENT.md. Docker users: docker build -f docker/build/x86_64/Dockerfile . or use docker-compose from docker/production/.

Daily commands: make (default target builds stash binary). Cross-compile: available via Makefile targets. Docker: docker build -f docker/build/x86_64/Dockerfile . then docker run. Dev server likely started via make run or ./stash once compiled. See Makefile and docs/DEVELOPMENT.md for exact targets.

🗺️Map of the codebase

  • cmd/stash/main.go — Application entry point that initializes the server, configuration, and all subsystems—every contributor must understand the startup flow.
  • graphql/schema/schema.graphql — Root GraphQL schema definition that defines the entire API surface; changes here ripple across code generation and client contracts.
  • go.mod — Dependency manifest with Go 1.25.0 requirement; critical for build reproducibility and understanding external library contracts.
  • internal/api/authentication.go — Core authentication logic that gates access to the API; foundational for security posture.
  • .github/workflows/build.yml — CI/CD pipeline that defines how code is tested, built, and released; understand this to trust build outputs.
  • gqlgen.yml — GraphQL code generation configuration that maps schema to Go resolver stubs; changes here affect developer workflow.
  • Makefile — Build orchestration file that documents the canonical build, test, and dev commands.

🛠️How to make changes

Add a new GraphQL Query or Mutation

  1. Define the new query/mutation and return type in the appropriate schema file (graphql/schema/types/*.graphql)
  2. Run GraphQL code generation to create resolver stubs (gqlgen.yml)
  3. Implement the resolver logic in internal/api (internal/api/*.go)
  4. Add unit test for the resolver (internal/api/*_test.go)

Add a new Domain Type or Entity

  1. Create a GraphQL type definition in a new or existing types file (graphql/schema/types/[domain].graphql)
  2. Define input types (Create/Update variants) in the same file (graphql/schema/types/[domain].graphql)
  3. Add input validation logic (internal/api/input.go)
  4. Implement changeset translation for mutations (internal/api/changeset_translator.go)

Add API Request Authentication or Authorization

  1. Add credential/token check logic (internal/api/authentication.go)
  2. Define context key for storing authenticated user (internal/api/context_keys.go)
  3. Apply middleware to routes that require auth (cmd/stash/main.go)

Optimize DataLoader for N+1 Query Prevention

  1. Define loader interface and configuration in loader package (internal/api/loaders/*.go)
  2. Use mockery to auto-generate loader mocks (.mockery.yml)
  3. Register loader in GraphQL context during request setup (cmd/stash/main.go)

🔧Why these technologies

  • Go 1.25.0 — Compiled, statically-typed language with excellent concurrency primitives (goroutines) and minimal runtime overhead for media serving and real-time operations.
  • GraphQL (gqlgen) — Strongly-typed API layer that auto-generates resolvers and allows clients to request exactly the fields they need, reducing over-fetching.
  • go-chi Router — Lightweight, idiomatic HTTP router with middleware support for composable request handling (CORS, logging, auth).
  • Docker — Multi-stage builds for reproducible deployments across dev/test/prod; CUDA variant supports GPU-accelerated media processing.
  • SQLite / SQL (goqu) — Embedded SQL database suitable for self-hosted deployments; goqu provides safe query builder DSL.

⚖️Trade-offs already made

  • Self-hosted Go binary vs. containerized service

    • Why: Stash targets individual users organizing personal media libraries who may have resource constraints and prefer minimal deployment complexity.
    • Consequence: Requires users to compile or download prebuilt binaries; Docker is optional but recommended for production.
  • GraphQL + REST-like HTTP endpoints (not pure REST)

    • Why: GraphQL provides flexibility for UI clients to fetch exactly what they need; coexists with traditional HTTP handlers for media serving.
    • Consequence: Requires developers to understand both GraphQL resolver patterns and chi middleware patterns.
  • Batch DataLoaders vs. lazy loading

    • Why: Prevents N+1 query problems when resolving nested fields in GraphQL queries.
    • Consequence: Added complexity in loader registration and context management; must be carefully orchestrated during request setup.
  • Generated code (gqlgen resolvers, loaders) vs. hand-written

    • Why: Reduces boilerplate and ensures schema-code consistency; auto-generation on schema changes minimizes manual sync errors.
    • Consequence: Developers must understand code generation configuration (gqlgen.yml) and respect generated file boundaries.

🚫Non-goals (don't propose these)

  • Real-time subscriptions over WebSocket (GraphQL schema exists but runtime support may be incomplete)
  • Multi-tenant authentication (single-instance, single-user focus by default)
  • Cross-platform mobile app (web UI only; mobile browser support implied)
  • Video transcoding or format conversion (assumed external or minimal in scope)
  • Distributed / clustered deployment (single-instance self-hosted design)

🪤Traps & gotchas

Database migrations: golang-migrate/migrate is required for schema changes; old migrations are immutable. GraphQL generation: changes to GraphQL schema (.graphql files, location inferred) require running gqlgen to regenerate Go types; manual edits to generated code are lost. CUDA builds: docker/build/x86_64/Dockerfile-CUDA exists but may require NVIDIA base image availability. Cross-compilation: Makefile likely has OS/arch-specific flags; Windows cross-compilation from Linux may fail. Plugin system: exact plugin interface not visible in file list—must read documentation or examples. SQLite concurrency: busy_timeout handling may be implicit in sqlx wrapper.

🏗️Architecture

💡Concepts to learn

  • GraphQL Code Generation (gqlgen) — Stash uses .gqlgenc.yml to auto-generate Go resolvers from schema; understanding this prevents manual edit loss and breaking type contracts
  • Database Migration Versioning (golang-migrate) — Schema changes are immutable versioned migrations; modifying old migrations breaks existing deployments, making this critical for stability
  • Content-based Hashing (Perceptual Hashing) — cmd/phasher implements video hashing for deduplication; perceptual hashes match visually similar content despite transcoding, enabling the core matching feature
  • Headless Browser Automation (chromedp) — Scrapers use chromedp to navigate JavaScript-heavy sites for metadata; understanding CDP protocol and sandbox escape risks is critical for plugin security
  • Plugin Architecture via Subprocess IPC (go-natefinch/pie) — Community scrapers likely run as isolated subprocesses via pie; isolating untrusted plugin code prevents malicious scrapers from compromising the server
  • Chi Router Middleware Chains — go-chi/chi/v5 with CORS (go-chi/cors) handles authentication, logging, and request routing; understanding middleware order is essential for auth/security fixes
  • SQLite Full-Text Search (FTS5) — Tag and performer search likely uses SQLite FTS5 for substring matching at scale; understanding query planning prevents N+1 bugs in search endpoints
  • jellyfin/jellyfin — Self-hosted media server with library management; same deployment target (local multimedia organization) but SFW-focused
  • nextcloud/nextcloud-server — Self-hosted file/media organization platform with extensible plugin system; alternative architecture for personal media control
  • stashapp/stash-box — Companion project providing shared metadata backend for Stash scrapers and community plugin data sources
  • stashapp/CommunityScrapers — Official repository of community-built scraper plugins for Stash; directly extends core functionality
  • 99designs/gqlgen — Direct dependency (v0.17.73); required for GraphQL code generation workflow in this repo

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive unit tests for cmd/stash/main.go and cmd/phasher/main.go

The cmd/stash/main_test.go exists but appears minimal. The entry points for both the main stash application and phasher utility lack comprehensive test coverage for initialization, configuration parsing, and error handling. This is critical for a production media organizer that handles user data.

  • [ ] Expand cmd/stash/main_test.go with tests for CLI flag parsing and configuration initialization
  • [ ] Create cmd/phasher/main_test.go with tests for phasher argument handling and core hashing logic
  • [ ] Add integration tests that verify proper initialization with various config file states (missing, malformed, valid)
  • [ ] Test error scenarios (missing database, permission issues, invalid paths)
  • [ ] Ensure tests cover the koanf configuration provider chain (env, file, flags)

Add missing database migration test suite for golang-migrate integration

The go.mod shows golang-migrate/migrate/v4 is a dependency, but there's no visible test coverage for database migrations in the file structure. For a stateful application that manages user libraries, migration testing is critical to prevent data loss or corruption during upgrades.

  • [ ] Create pkg/database/migrations_test.go or similar to test all migration files
  • [ ] Add tests that verify migrations run in correct order using test SQLite databases
  • [ ] Test rollback scenarios for each migration
  • [ ] Verify schema consistency before/after migrations
  • [ ] Add tests for edge cases (concurrent migrations, missing migration files, corrupted state)

Create missing CI workflow for Docker image security scanning and test coverage reporting

The .github/workflows directory has build.yml and golangci-lint.yml, but missing are critical workflows for Docker image vulnerability scanning (important given the docker/ directory structure) and test coverage tracking. These gaps make it harder to catch supply chain issues and code quality regressions.

  • [ ] Create .github/workflows/docker-security-scan.yml using Trivy or similar to scan built Docker images for CVEs
  • [ ] Add coverage reporting step to build.yml that uploads coverage data to Codecov or Coveralls
  • [ ] Configure .github/workflows to run security scan on docker/build and docker/ci Dockerfiles
  • [ ] Add badge to README.md showing current test coverage percentage
  • [ ] Configure workflow to fail builds if Docker vulnerabilities exceed severity threshold

🌿Good first issues

  • Add unit tests for cmd/phasher/main.go (file exists but is untested; video hashing is critical path)
  • Document GraphQL schema changes in DEVELOPMENT.md with before/after examples of gqlgen regeneration workflow
  • Create missing Dockerfile example for development (docker/ has CI and production but no explicit dev Dockerfile with hot-reload setup)

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 01a7583 — Refresh file info counter after deleting a file (#6841) (dev-null-life)
  • 9234979 — [ci] add explicit flow for makefile, add make install (#6877) (feederbox826)
  • 46f72e5 — [docker] bump cuda, node version (#6890) (feederbox826)
  • 3afe292 — Align release Dockerfiles with Go 1.25 for backend builds. (#6889) (Stash-KennyG)
  • db4eabe — New: Allow Description and Alias on Tag Creation in Scene Tagger (#6872) (Gykes)
  • 1ec5583 — Bump golang.org/x/image from 0.18.0 to 0.38.0 (#6774) (dependabot[bot])
  • 2b29207 — Upgrade go to 1.25.9 and golangci-lint (#6869) (WithoutPants)
  • 98fd026 — Update go.sum (WithoutPants)
  • 3f83a84 — Bump github.com/antchfx/xpath from 1.3.5 to 1.3.6 (#6763) (dependabot[bot])
  • af6491a — Pnpm dedupe and overrides (#6868) (WithoutPants)

🔒Security observations

Failed to generate security analysis.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Mixed signals · stashapp/stash — RepoPilot