Scyros

Tutorial

This tutorial walks through a complete, small-scale mining study with Scyros. In this example study, we use Scyros to collect Java and Go functions that use concurrency primitives. The tutorial follows Scyros 0.5.0.

Prerequisites

To follow this tutorial, you need Scyros installed and a GitHub personal access token to communicate with the GitHub REST API.

Scyros

Install Scyros following the installation instructions, and confirm that it is on your path:

scyros --version

scyros --help lists the available subcommands, and scyros <subcommand> --help describes all the options for a given subcommand.

GitHub token

To sample repositories, query their metadata or download them, you need a personal access token for the GitHub REST API. In GitHub, go to Settings โ†’ Developer settings โ†’ Personal access tokens โ†’ Fine-grained tokens, generate a token with public repository access only (see GitHub's documentation for details), and save it in a file named tokens.csv with the following format:

tokens.csv
token
paste your token here

Do not commit or share this file.

Pipeline overview

Running a study with Scyros involves executing a series of commands that process data in a pipeline. Each step of the pipeline reads the CSV file written by the previous step and writes a new one, so every intermediate stage of the sample can be inspected, reported and rerun independently.

Step Purpose Output
1 Draw a random sample of 2,000 repositories ids.csv
2 Remove duplicate IDs unique_ids.csv
3 Remove forks ids_without_forks.csv
4 Collect repository metadata metadata.csv
5 Exclude trivial repositories filtered_metadata.csv
6 Collect the languages of each repository languages.csv
7 Keep repositories containing Java or Go java_go_repos.csv
8 Download source code and keep matching files repos/, projects.csv, files.csv
9 Remove duplicate files unique_files.csv, duplicates.csv
10 Extract matching functions and compute their metrics functions.csv, function_logs.csv

Steps

1. Sample repositories

GitHub assigns repository IDs in roughly chronological order. Scyros samples IDs either at random or in increasing order (--mode linear). Due to the way the REST API works, IDs are retrieved in contiguous chunks: in random mode, each request starts from an ID drawn uniformly at random, with replacement, and returns the IDs of the next 100 public repositories from that point. The --min and --max flags set the range of IDs Scyros queries, and -n sets the number of IDs to collect. In this tutorial, we sample 2,000 repository IDs at random, from the first repository on GitHub up to the default maximum ID, which corresponds to 5 January 2026. To execute this step, run the following command:

scyros ids -t tokens.csv -n 2000 -o ids.csv

The sampled repositories are stored in an output CSV file (here, ids.csv) with the repository's ID, where it comes from (here, always GitHub), its name, a link to it on GitHub, whether it is a fork (1) or not (0), and the request in which it was sampled. Every later step copies the first columns, so each row of each file can be traced back to its repository on GitHub.

ids.csv
repo_id,source,repo_name,url,fork,request_number
412873310,github,alice/chat-server,https://github.com/alice/chat-server,0,0
412873318,github,bob/dotfiles,https://github.com/bob/dotfiles,0,0
412873325,github,carol/spring-petclinic,https://github.com/carol/spring-petclinic,1,0
...

The random seed is fixed (configurable with -s), so running the same command draws the same IDs. In addition, if the process is paused or interrupted, it can be resumed by rerunning the command without losing earlier results or compromising reproducibility.

2. Remove duplicate IDs

Because sampling is with replacement, the same repository can be recorded more than once. To remove duplicate IDs, run the following command:

scyros duplicate_ids -i ids.csv -o unique_ids.csv

The result is stored in unique_ids.csv. With a sample of 2,000 IDs, duplicates are very unlikely.

3. Remove forks

To avoid overrepresenting popular projects, we also remove forks with the following command:

scyros forks -i unique_ids.csv -o ids_without_forks.csv

The result is stored in ids_without_forks.csv. On average, about a third of sampled repositories are forks, so expect this step to shrink the sample by roughly that much.

4. Collect metadata

Run the following command to retrieve each repository's metadata:

scyros metadata -i ids_without_forks.csv -t tokens.csv -o metadata.csv

This metadata includes each repository's primary language, size (in kB), creation, last push and last update dates (as Unix timestamps), popularity counts and licence. This step sends one request per repository and is the slowest of the tutorial. Adding -n 4 sends four requests at the same time; the rows are then written in the order in which the requests complete.

Repositories deleted or made private since sampling cannot be queried. They stay in the output with an error code in the error column, such as E404, and empty metadata. Run scyros explain E404 to see what a code means.

metadata.csv
repo_id,source,repo_name,repo_license,latest_commit,url,error,language,created,pushed,updated,fork,disabled,archived,stars,forks,issues,has_issues,watchers_count,subscribers,size
412873310,github,alice/chat-server,MIT License,,https://github.com/alice/chat-server,,Go,1631015230,1712345678,1712345690,0,0,0,4,1,0,1,4,1,612
412873318,github,bob/dotfiles,unknown,,https://github.com/bob/dotfiles,,Shell,1631015302,1633700000,1633700012,0,0,0,0,0,0,1,0,1,18
412873330,github,erin/notes,,,https://github.com/erin/notes,E404,,,,,,,,,,,,,,
...

5. Filter from metadata

Many public repositories are empty, are not software projects, or are tiny projects abandoned shortly after their creation (see Maj et al., The Fault in Our Stars). Since we want to study engineered software projects, we exclude these. Following the authors' recommendations, Scyros provides a command to filter out repositories smaller than a given size (-s, in kB) or younger than a given age (-a, in days). Age is defined as the number of days between the creation date and the last push, so it measures how long a repository was actively developed rather than how long ago it was created. The --non-code flag filters out repositories without a language assigned by GitHub, which most often hold only documentation, data or binaries. The same authors show that selecting repositories by popularity metrics such as stars biases the sample, so we do not filter on them. Here, we keep only repositories that were active for at least 60 days, are at least 50 kB and have a language assigned by GitHub. The filtered dataset is stored in filtered_metadata.csv.

scyros filter_metadata -i metadata.csv -s 50 -a 60 --non-code -o filtered_metadata.csv

filter_metadata runs locally on metadata.csv and sends no requests, so you can compare different thresholds cheaply. In our experience, about 10% of the repositories remain after this step.

6. Query languages

GitHub's primary language understates multi-language repositories: a Kotlin project may also contain Java. This command records the full language breakdown of each repository, in bytes.

scyros languages -i filtered_metadata.csv -t tokens.csv -o languages.csv
languages.csv
repo_id,source,repo_name,repo_license,latest_commit,url,error,languages
412873310,github,alice/chat-server,MIT License,,https://github.com/alice/chat-server,,Go:80312;JavaScript:15420;Dockerfile:310
412873402,github,dan/budget-app,Apache License 2.0,,https://github.com/dan/budget-app,,Kotlin:120344;Java:5120

7. Keep repositories in Java and Go

We now keep only the repositories that contain Java or Go code. To do so, we create a JSON config file that lists the languages included in our study:

config.json
{
  "languages": [
    {"name": "java"},
    {"name": "go"}
  ]
}

Language names are case-insensitive, so java matches GitHub's Java. Then run the following command to filter the repositories by language. The output is stored in java_go_repos.csv.

scyros filter_languages -i languages.csv -l config.json -o java_go_repos.csv

Here, the config file is passed with -l because this command only reads its list of languages. Later commands also read its keywords and take the same file with -k.

8. Download the code

The next step is to download the code of the repositories we want to study. The download command fetches each repository and keeps only the Java and Go files that contain at least one concurrency-related keyword. To specify this, we extend the config.json file from step 7 with the file extensions to consider for each language and the keywords that relate to concurrency:

config.json
{
  "languages": [
    {
      "name": "java",
      "extensions": [".java"],
      "keywords": ["synchronized", "volatile", "Thread",
                   "ExecutorService", "CompletableFuture"]
    },
    {
      "name": "go",
      "extensions": [".go"],
      "keywords": ["go", "chan", "WaitGroup", "Mutex"]
    }
  ],
  "keywords": ["lock", "unlock"]
}

Each language now has a list of file extensions that Scyros keeps after downloading a repository, and a list of keywords used to filter files. The top-level keywords (lock and unlock) are matched in every language. By default, keywords are matched as whole words and case-insensitively; see the documentation for the flags that change this. Then run:

scyros download -i java_go_repos.csv -t tokens.csv -k config.json -d repos -p projects.csv -f files.csv

Each repository is downloaded from its default branch into the directory given by -d (here, repos/). Files outside the configured extensions, and files containing none of the keywords, are then deleted. The command writes per-repository statistics to the file given by -p (here, projects.csv) and per-file statistics to the file given by -f (here, files.csv). In files.csv, the number of keyword matches is stored in a column named after the relative path of the config file (here, config.json). This filter is deliberately permissive: it also matches keywords in comments and string literals. Manually inspecting a random sample of the matching files helps assess its precision.

Both files record the commit downloaded, in the latest_commit column, so others can retrieve identical code, provided the repository is still public and its history has not been rewritten. Each row of files.csv also gives the path of the file inside its repository and a link to it on GitHub at this commit:

files.csv
repo_id,source,repo_name,repo_license,latest_commit,local_path,file,url,language,loc,words,config.json
412873310,github,alice/chat-server,MIT License,9f2c1a7e5b0d4c3a8e6f1b2d9c0a7e4f3b5d8c1a,repos/0/412873310/alice-chat-server-9f2c1a7/server/hub.go,server/hub.go,https://github.com/alice/chat-server/blob/9f2c1a7e5b0d4c3a8e6f1b2d9c0a7e4f3b5d8c1a/server/hub.go,go,180,655,9
...

To fix the commits before downloading, for example to share the list of repositories in advance, run scyros latest_commit -i java_go_repos.csv -t tokens.csv -o pinned_repos.csv and give pinned_repos.csv to download, which then fetches these commits.

9. Remove duplicate files

Now that we have repository-level statistics, we turn to files and functions. From this step on, files are treated as a single pool, independent of their repository. As Lopes et al. show in their empirical study on code duplication, GitHub contains substantial cross-repository duplication, such as libraries, generated code and files that users forget to add to .gitignore (e.g. node_modules). Such files end up overrepresented in file- and function-level statistics, so Scyros provides a command to deduplicate files across repositories. With the flag -s bow (bag of words), two files are duplicates when they contain the same tokens with the same frequencies, regardless of order and whitespace. The -s exact option requires byte-identical files, and -s overlap also detects near-miss clones that differ by a few statements, at a much higher cost.

scyros duplicate_files -i files.csv -s bow -o unique_files.csv -m duplicates.csv

The file given by -o (here, unique_files.csv) keeps one representative per group, with a count column giving the group size. The file given by -m (here, duplicates.csv) maps each file to its representative. Feel free to try different deduplication strategies, although at this scale all three produce similar results.

10. Extract the functions

The goal of our example study is to extract functions that use concurrency primitives. For this last step, Scyros provides a command that parses the files with Tree-sitter, extracts their functions and keeps only those whose body contains at least one keyword. Keywords are matched with the same rules as in step 8, but comments and string literals are ignored, so this filter is more precise than the file-level one. Each function is written to its own file, and a summary CSV file (here, functions.csv) records metrics for each function, such as its size, the number of keyword occurrences and the programming constructs it contains (loops, conditionals, function calls), with a link to its first line on GitHub. A file-level summary (function_logs.csv) gives the number of functions found and kept in each file.

scyros parse -i unique_files.csv -k config.json -o functions.csv -l function_logs.csv

Below is an example of a Go function extracted by Scyros. Extracted functions are stored in a .functions directory next to their source file, in a file named after the function's start line and column (here, line 42, column 1).

repos/0/412873310/alice-chat-server-9f2c1a7/server/hub.go.functions/42-1
func broadcastAll(urls []string) []string {
	var wg sync.WaitGroup
	results := make([]string, len(urls))
	for i, url := range urls {
		wg.Add(1)
		go func(i int, url string) {
			defer wg.Done()
			results[i] = fetch(url)
		}(i, url)
	}
	wg.Wait()
	return results
}

Its row in functions.csv links to it on GitHub:

functions.csv
repo_id,source,repo_name,repo_license,latest_commit,local_path,file,function_name,line,column,url,error,local_function_path,language,loc,words,config.json,loop_statements,loop_nestings,if_statements,if_nestings,functions_calls,function_calls_nestings,params,param_kw_match,return_kw_match,parse_error_line,parse_error_column
412873310,github,alice/chat-server,MIT License,9f2c1a7e5b0d4c3a8e6f1b2d9c0a7e4f3b5d8c1a,repos/0/412873310/alice-chat-server-9f2c1a7/server/hub.go,server/hub.go,broadcastAll,42,1,https://github.com/alice/chat-server/blob/9f2c1a7e5b0d4c3a8e6f1b2d9c0a7e4f3b5d8c1a/server/hub.go#L42,,repos/0/412873310/alice-chat-server-9f2c1a7/server/hub.go.functions/42-1,go,13,40,2,1,1,0,0,6,1,1,0,0,,
...

This concludes the tutorial. For more information on the commands, their options and the format of the input and output files, please refer to the documentation. If you find a bug or would like to see a new feature implemented, please open an issue on GitHub.