Agentic AI coding – Scenarios that has reconciliation tied to it.

Here is a self identified item that I ran into recently. I wanted a dashboard view of all my GITHUB projects. I wanted to use one shot to remove them. This would prevent going through the confirmation process in GITHUB. So Agentic AI asked me to look at the GITHUB API portal. I was to build a code base that would allow me to manually add a repo from my portal. It would also allow me to delete a repo from my portal. Agentic AI in my case Windsurf was able to get that done for me in few prompts. If I had to build out the design and logic myself, it would have taken me more time. I know this with certainty. I would have spent a longer time understanding the GitHub API portal integrations.

So now that Agentic AI had built the code, time to test to see if everything was working well. That is when I realized I did not have a straightforward GITHUB repo. My repos were tied to my organization, private repos, and public repos. So, I would need a comprehensive reconciliation strategy. This is to make sure that when I build my dashboard, it encompasses everything under my user name. That is where nightmare starts. Agentic AI code build out. It built me the code that was something like this

package me.sathish.my_github_cleaner.base.repositories;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class GitHubService {
    private static final String GITHUB_API_BASE_URL = "https://api.github.com";
    private final WebClient webClient;
    @Value("${github.token:}")
    private String githubToken;
    public GitHubService() {
        this.webClient = WebClient.builder()
                .baseUrl(GITHUB_API_BASE_URL)
                .defaultHeader(HttpHeaders.ACCEPT, "application/vnd.github.v3+json")
                .build();
    }
    public Mono<List<GitHubRepository>> getUserRepositories(String username) {
        return webClient
                .get()
                .uri("/users/{username}/repos?per_page=100&sort=updated", username)
                .headers(headers -> {
                    if (githubToken != null && !githubToken.isEmpty()) {
                        headers.setBearerAuth(githubToken);
                    }
                })
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<List<GitHubRepository>>() {});
    }
    public Mono<List<GitHubRepository>> getAuthenticatedUserRepositories() {
        if (githubToken == null || githubToken.isEmpty()) {
            return Mono.error(new RuntimeException("GitHub token is required for authenticated requests"));
        }
        return webClient
                .get()
                .uri("/user/repos?per_page=100&sort=updated")
                .headers(headers -> headers.setBearerAuth(githubToken))
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<List<GitHubRepository>>() {});
    }
    public Flux<GitHubRepository> getAllUserRepositoriesPaginated(String username) {
        return getAllRepositoriesRecursive(1, username, "/users/{username}/repos");
    }
    public Flux<GitHubRepository> getAllAuthenticatedUserRepositoriesPaginated() {
        if (githubToken == null || githubToken.isEmpty()) {
            return Flux.error(new RuntimeException("GitHub token is required for authenticated requests"));
        }
        return getAllRepositoriesRecursive(1, null, "/user/repos");
    }
    private Flux<GitHubRepository> getAllRepositoriesRecursive(int page, String username, String endpoint) {
        return webClient
                .get()
                .uri(uriBuilder -> {
                    if (username != null) {
                        return uriBuilder
                                .path(endpoint)
                                .queryParam("per_page", 100)
                                .queryParam("page", page)
                                .queryParam("sort", "updated")
                                .build(username);
                    } else {
                        return uriBuilder
                                .path(endpoint)
                                .queryParam("per_page", 100)
                                .queryParam("page", page)
                                .queryParam("sort", "updated")
                                .build();
                    }
                })
                .headers(headers -> {
                    if (githubToken != null && !githubToken.isEmpty()) {
                        headers.setBearerAuth(githubToken);
                    }
                })
                .retrieve()
                .bodyToFlux(GitHubRepository.class)
                .expand(repo -> {
                    // Continue fetching next page if current page has 100 items (max per page)
                    return webClient
                            .get()
                            .uri(uriBuilder -> {
                                if (username != null) {
                                    return uriBuilder
                                            .path(endpoint)
                                            .queryParam("per_page", 100)
                                            .queryParam("page", page + 1)
                                            .queryParam("sort", "updated")
                                            .build(username);
                                } else {
                                    return uriBuilder
                                            .path(endpoint)
                                            .queryParam("per_page", 100)
                                            .queryParam("page", page + 1)
                                            .queryParam("sort", "updated")
                                            .build();
                                }
                            })
                            .headers(headers -> {
                                if (githubToken != null && !githubToken.isEmpty()) {
                                    headers.setBearerAuth(githubToken);
                                }
                            })
                            .retrieve()
                            .bodyToFlux(GitHubRepository.class)
                            .take(100);
                });
    }
}

Now in generated code line #53 and #58 have a hard coded number of page number value of 1. The code then called different URLs. These were the URLs that the Agentic Code generator determined as the best API endpoint for the prompt. The front-end for this code is using REACT. The kicker in my head was the pagination in the front-end code base was for 100 total repositories. But, the paginated code was redisplaying based on the hard-coded page value.

I was not capable of reconciling the 100 repositories that I was in my GITHUB account to the controller-service-repo code. So to debug this whole thing, I asked the Agentic model to write test case. That dragged me with code that was based of JUNIT instead of Spring based framework. All of this leads to more junk code than building the code base for an reconciliation ask. The next deep hole was adding the read repos from the above code base and saving them to the database. I had the database entity to make sure the GITHUB repo name and repo create date to be unique. So when the above service was called it was saving only first 10 records.

I deleted the entire code base that was connecting to GitHub. I started reading the GITHUB documentation. Here is my updated code.

package me.sathish.my_github_cleaner.base.github;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.List;
import java.util.Optional;
import me.sathish.my_github_cleaner.base.repositories.GitHubRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class GitHubService implements GitHubServiceConstants {
    private static final Logger log = LoggerFactory.getLogger(GitHubService.class);
    private static final String TOKEN_REQUIRED_ERROR = "GitHub token is required for authenticated requests";
    private static final int PER_PAGE = 100;
    private static final String SORT_PARAM = "updated";
    private static final String AUTH_USER_REPOS_PATH = "/user/repos";
    private final Environment environment;
    private final RestTemplate restTemplate;
    private final String githubToken;
    public GitHubService(Environment environment) {
        this.environment = environment;
        this.githubToken = environment.getProperty(GITHUB_TOKEN_KEY);
        this.restTemplate = new RestTemplate();
    }
    // Fetch a specific repository for the authenticated user
    public Optional<GitHubRepository> getAuthenticatedUserRepository(String repoName) {
        validateToken();
        String username = environment.getProperty(GITHUB_USERNAME_KEY);
        String orgName = environment.getProperty(GITHUB_ORG_KEY);
        if (username == null || username.isEmpty()) {
            throw new RuntimeException("GitHub username is not configured");
        }
        if (orgName == null || orgName.isEmpty()) {
            log.error("GitHub organization name is not configured.");
        }
        String url = String.format("%s/repos/%s/%s", GITHUB_API_BASE_URL, username, repoName);
        String orgUrl = String.format("%s/repos/%s/%s", GITHUB_API_BASE_URL, orgName, repoName);
        HttpHeaders headers = new HttpHeaders();
        setAuthorizationHeader(headers);
        HttpEntity<Void> entity = new HttpEntity<>(headers);
        // Try user repository first
        try {
            log.debug("Requesting repository: {}", url);
            ResponseEntity<GitHubRepository> response =
                    restTemplate.exchange(url, HttpMethod.GET, entity, GitHubRepository.class);
            // If response is null or bad, try the organization URL
            if (response.getBody() == null && orgName != null && !orgName.isEmpty()) {
                log.error("Primary response was null, trying organization URL: {}", orgUrl);
                ResponseEntity<GitHubRepository> orgResponse =
                        restTemplate.exchange(orgUrl, HttpMethod.GET, entity, GitHubRepository.class);
                return Optional.ofNullable(orgResponse.getBody());
            }
            return Optional.ofNullable(response.getBody());
        } catch (Exception e) {
            log.error("Exception occurred while fetching user repository {}: {}", repoName, e.getMessage());
            // If exception occurred and org name is available, try organization URL
            if (orgName != null && !orgName.isEmpty()) {
                try {
                    log.error("Trying organization URL due to exception: {}", orgUrl);
                    ResponseEntity<GitHubRepository> orgResponse =
                            restTemplate.exchange(orgUrl, HttpMethod.GET, entity, GitHubRepository.class);
                    return Optional.ofNullable(orgResponse.getBody());
                } catch (Exception orgException) {
                    log.error("Failed to fetch organization repository {}: {}", repoName, orgException.getMessage());
                }
            }
            return Optional.empty();
        }
    }
    public List<GitHubRepository> fetchAllPublicRepositoriesForUser(String username) {
        String url = buildUri(AUTH_USER_REPOS_PATH);
        HttpHeaders headers = new HttpHeaders();
        setAuthorizationHeader(headers);
        HttpEntity<Void> entity = new HttpEntity<>(headers);
        ResponseEntity<List<GitHubRepository>> response = restTemplate.exchange(
                url, HttpMethod.GET, entity, new org.springframework.core.ParameterizedTypeReference<>() {});
        return response.getBody();
    }
    private void setAuthorizationHeader(HttpHeaders headers) {
        if (githubToken != null && !githubToken.isEmpty()) {
            headers.setBearerAuth(githubToken);
        }
    }
    private String buildUri(String path, Object... params) {
        String uri;
        if (params != null && params.length > 0 && params[0] != null) {
            String baseUri = String.format("%s?per_page=%d&sort=%s", path, PER_PAGE, SORT_PARAM);
            uri = baseUri.replace("{username}", params[0].toString());
        } else {
            uri = String.format("%s?per_page=%d&sort=%s", path, PER_PAGE, SORT_PARAM);
        }
        log.debug("Built URI: {}", uri);
        return GITHUB_API_BASE_URL + uri;
    }
    private void validateToken() {
        if (githubToken == null || githubToken.isEmpty()) {
            throw new RuntimeException(TOKEN_REQUIRED_ERROR);
        }
    }
}

Code was much more understandable for me and debug it through what was going in the project. I had a better control and read on what the repository code was being read. If we compare both code bases, I first noticed the need to externalize the page number details. Then was the authorization part of GITHUB where I pulled it out as environment variables. Exception handling was making more sense for how I would have preferred it to be.

Agentic AI coding is good in coding scenarios where there is no reconciliation logic tied to it. Nevertheless, when using code generation, it takes a lot more time to debug. This is especially true if something is not syncing up well with your expected results.